Repository navigation
docs: refresh documentation; fix robots, env schema and Claude fetchers - #139
Conversation
README: CI section described a ci.yml that no longer exists; rewritten for lint-and-test.yml (incl. Semgrep, disabled E2E) and build-and-deploy.yml (boot checks, single build). Fixed dev/generate/build/seed scripts, env variable table (NEXT_PUBLIC_SERVER_URL, NEXT_PUBLIC_STATUS_PAGE_URL, RESUME_REDIRECT_URL_BASE), API routes and the project structure tree. AGENTS: dev no longer starts Storybook, all four authenticated SSE channel prefixes, Semgrep convention, plugins/stories/llms.txt notes, and a refreshed Known Issues list (pdfjs-dist pin replaces the removed postcss-merge-rules one). llms.txt: removed references to src/access, the heartbeat route and the next.config env block.
Also drops .github/actionlint.yaml, whose only entry configured the removed ci.yml, and updates README and AGENTS for the new paths. global-error.tsx stays at the app root: Next only resolves it there.
global-error replaces the root layout, so the frontend CSS and font variables its classes rely on were never loaded and the page rendered unstyled.
Drops the per-bot split that limited everything except search engines and link-preview bots to /llms.txt. Tests follow the single rule.
…cpack @playwright/test was on ~1.63.0 while playwright was on ~1.64.1 and the Docker image on v1.62.1. The sameRange group never caught it: syncpack compares one dependency across packages, not two dependencies with each other. pinVersion does. e2e-docker.sh now reads the image tag from the installed @playwright/test.
Nothing read it; all AI calls go through src/lib/anthropic. Removed from the env schema, global.d.ts, .env.test, the deploy workflow and the docs.
…schema The heartbeatPing handler already read it; it is optional, so an unset or empty value still passes validation.
robots.txt only opens the site when SERVER_HOST is daniel.heene.io, the same signal the PDF draft watermark uses. NODE_ENV is production in every deployed image, so it cannot tell the environments apart.
…l-error and the playwright pin
robots.txt and the PDF draft watermark compared SERVER_HOST with a hardcoded daniel.heene.io. Both now go through isProductionHost(), which compares it with the new required PRODUCTION_HOST variable and treats an unset value as not production.
…s, move to Haiku 5.5 The meta description fetcher sent only the page URL to a model that cannot fetch, so the text was invented from the slug. It now sends the document's own title and text and clamps the result to 160 characters. All four fetchers share generateAnthropicText(): claude-haiku-5-5 at low effort, an output limit, a timeout, and an error instead of a truncated or declined answer. Prompts wrap their input in tags and state language, voice and what to leave untranslated; alt text sees a 1024px image instead of 512px. The inline generate actions check the session, the alt action only fetches same-origin media, and a failed alt generation no longer fails the image upload. The fetcher modules drop 'use server'. Removes the never-wired meta description hook.
Translation is read closely and runs rarely, so it gets the stronger model; alt text, excerpts and meta descriptions stay on Haiku 5.5.
Adds the Claude helpers, session check, production detection, CI workflows, Dockerfile and release config; gives every list item a single link; moves secondary material into the Optional section.
Next ignores robots.ts and manifest.ts inside a route group: neither route was compiled, so /robots.txt would have 404ed after the earlier move, and /manifest.webmanifest already does on production. sitemap.ts is unaffected and stays in app/(frontend).
bun run analyze builds in compile mode with @next/bundle-analyzer and runs scripts/check-bundle-size.mjs, which reports the gzipped first-load JavaScript of every public route. The new PR job "Build & Bundle Size" runs the same build with .env.test, publishes the table to the job summary, uploads the analyzer reports and fails above 265 kB. 170 kB, the usual recommendation, is reported as a warning: the framework chunks alone are 189 kB, so no route can meet it today (largest route: 251 kB).
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (1 file)
Fix these issues in Kilo Cloud Previous Review Summaries (9 snapshots, latest commit 29e004f)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 29e004f)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (4 files)
Fix these issues in Kilo Cloud Previous review (commit afe6036)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (7 files)
Fix these issues in Kilo Cloud Previous review (commit 8ef687e)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (27 files)
Fix these issues in Kilo Cloud Previous review (commit 3066c09)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
WARNING
Files Reviewed (33 files)
Fix these issues in Kilo Cloud Previous review (commit bb0a381)Status: No Issues Found | Recommendation: Merge Incremental review of changes since Files Reviewed (33 files)
Previous review (commit 8931a21)Status: No Issues Found | Recommendation: Merge Incremental review of changes since Files Reviewed (2 files)
Previous review (commit 8dbf241)Status: No Issues Found | Recommendation: Merge Files Reviewed (33 files)
Previous review (commit c01ef27)Status: No Issues Found | Recommendation: Merge Files Reviewed (4 files)
Previous review (commit 8ccfb80)Status: No Issues Found | Recommendation: Merge Files Reviewed (43 files)
Reviewed by free · Input: 20K · Output: 1.2K · Cached: 69.6K |
The job built against .env.test. The analyzer and the budget check stay as local tooling (bun run analyze).
Drops scripts/check-bundle-size.mjs, the bundle:check script and the size budget. bun run analyze is now a plain ANALYZE=true build.
next/link threw on an undefined href and failed the prerender of /resume when a resume document has no url. A file download is not a route, so the button is now an <a download>, and it is skipped when the document has no url.
Next 16.4.6, tailwind-variants 4, tailwindcss 4.3.5, takumi-js 2.20, oxlint 1.93, oxfmt 0.78, Playwright 1.65 (both packages, pin updated). syncpack now sorts scripts; the lefthook package-json step also matches bun.lock.
…a definitions generatorFlags is required with an empty default and reads as [] when unset; GeneratorFlag and GeneratorFlags are JSON-schema definitions referenced by the generated types. The two create calls that omitted the field now pass an empty array.
generatorFlags no longer runs a beforeChange hook; the hook and its test are deleted.
Updates CI and docs for the new script names, trims syncpack config comments, removes unused tailwind-merge and bumps React.
sameRange compares one dependency across packages, so it never caught react 19.3.1 next to react-dom 19.3.0. Payload, Storybook, Vitest, Playwright and React are pinned with pinVersion instead; react-dom moves to ^19.3.1.
… config global.d.ts derives ProcessEnv from the Zod schema. next.config.ts splits env parsing and the dev tunnel into functions and writes the parsed values back, unsetting empty optional variables instead of storing the string 'undefined'. tsconfig.json is valid JSON again. oxlint keeps typeAware off until the strict-mode refactor.
…g fetchers fetchPostBySlug and fetchDocumentBySlug join fetchPageBySlug; the blog post and resume routes use them and render inside a Suspense shell. Published reads filter on _status, preview reads do not, and resume documents (no drafts) are not filtered at all. Topic deletion counts rows in the references collection. The initial migration creates its pages through the migration's own payload and transaction.
| * replace Node's default signal behaviour and let the wrapper's own | ||
| * exit-code handling be pre-empted by a hardcoded `process.exit(0)`. | ||
| */ | ||
| process.once('exit', () => { |
There was a problem hiding this comment.
WARNING: createTunnel can resolve to null (on error or non-zero exit), but the process.once('exit') handler dereferences childProcess.killed and childProcess.kill('SIGTERM') without a null check. If the tunnel fails and the process exits, this throws.
The directive turns every export of a module into a server action, a publicly callable endpoint. It was on 23 modules (block renderers, PDF builders, fetchers, Payload hooks, widget data loaders) and two functions that no client component calls. The 21 remaining directives mark functions a client component invokes or receives as a prop.
UmamiWidget.data.ts is a 'use server' module, so its getToken export was a callable endpoint returning the Umami bearer token. It now lives in src/lib/umami/getToken.ts, a plain server module.
fetchWebsite, fetchStats, fetchEvents, fetchPaths and fetchPageViews are server actions and returned analytics data to any caller. Each now calls requireSession() first.
Summary
Started as a documentation refresh and grew into the fixes and cleanups that turned up along the way: crawler rules and production detection, the Claude fetchers, a dependency and tooling round, and a rework of how pages, posts and resume documents are loaded by slug. 26 commits.
Changes
Docs
llms.txtrewritten against the code: CI section (theci.ymlit described no longer exists), scripts, env variable table, routes, project structure, known issues.PREVIEW_SECRET.Crawlers and production detection
robots.txtserves one rule for every crawler on production andDisallow: /everywhere else. This opens production to all crawlers, AI training crawlers included.PRODUCTION_HOST.isProductionHost()compares it withSERVER_HOST;robots.tsand the PDF draft watermark use it instead of a hardcoded host.robots.ts,manifest.tsandglobal-error.tsxstay at the root ofapp/: Next ignores all three inside a route group.manifest.tswas inside(frontend), which is why/manifest.webmanifestreturns 404 on production today.sitemap.tsmoves intoapp/(frontend)/, where it works.global-error.tsxloads the stylesheet and fonts itself (it rendered unstyled).Environment
OPENAI_API_KEYremoved (nothing read it);STATUS_PAGE_WORKER_HEARTBEAT_PUSH_URLdeclared as optional.process.envis typed from the Zod schema inglobal.d.tsinstead of a hand-kept list.next.config.tssplits env parsing and the dev tunnel into functions and writes the parsed values back toprocess.env; empty optional variables are unset.Claude calls (
src/lib/anthropic/)claude-haiku-5-5at low effort, output limit, timeout, and an error instead of a truncated or declined answer. Resume translation usesclaude-sonnet-5-5.Content loading
fetchPostBySlugandfetchDocumentBySlugjoinfetchPageBySlug; the blog post and resume routes use them and render inside a<Suspense>shell like/[slug]already did._status, preview reads do not. Resume documents have no drafts and are not filtered.fetchPageBySlugno longer defaults to German (German is only used for the PDF)./resume/latestresolves throughfetchDocumentBySlug; resume pages get a canonical URL pointing at the real slug.<a download>instead ofnext/link, and is skipped when a document has no URL (this crashed the prerender of/resume).Payload
generatorFlagsdefaults to and reads as an empty array;GeneratorFlag/GeneratorFlagsare shared JSON-schema definitions in the generated types. ThenormalizeIncomingFlagshook is removed.payloadand transaction.Dependencies and tooling
tailwind-mergeremoved (no longer imported).pinVersion.sameRangecompares one dependency across packages, so in this single-package repo it never caught a mismatch such asreactnext to an olderreact-dom.deps:*topackages:*;package.jsonscripts are sorted by syncpack; the separateformatscript is gone (lint:fixcovers it).@next/bundle-analyzerwired intonext.config.ts;bun run analyzeruns a build withANALYZE=true. No CI job, no budget.e2e-docker.shderives its image tag from the installed@playwright/test.CI
actions/cacheandactions/upload-artifactpinned to commit SHAs.build-and-deploy.ymlrestores.next/cachebetween builds..github/actionlint.yaml(only configured the removedci.yml) is deleted.Before merging
PRODUCTION_HOSTmust be set in the Dokploy app and worker environments. It exists in the GitHub environments. Without it production servesDisallow: /and PDFs carry the DRAFT watermark.OPENAI_API_KEYsecret can be deleted from GitHub and Doppler.Testing
bun run lint,bun run typecheck,bun run test(948 tests),bun run packages:lintand a local production build (bun run build, 95 static pages) pass.notFound()runs inside a<Suspense>shell.typeAware) stays off until a separate strict-mode pass.