# NoteLett Production Readiness Handoff Roadmap Date: May 5, 2026 Repo: `learning_ai_notes` Common platform repo: `../learning_ai/learning_ai_common_plat` Status: Ready for incremental implementation ## Purpose Use this document as the source of truth for finishing NoteLett end to end and making it production ready. It reconciles the existing product roadmaps, current code, and the reusable capabilities in `learning_ai_common_plat`. The implementation rule is simple: complete one checklist item or one small cluster at a time, run the stated verification, commit with the repo convention, push, and record the commit hash in this document before moving on. ## Current Baseline This baseline is from repo inspection on May 5, 2026. | Area | Current state | | --- | --- | | Backend | Fastify 5 product backend with notes, workspaces, relationships, tasks, artifacts, agent actions, saved views, prompts, intake, collaborators, shares, versions, Palace integration, MCP tools, Cosmos registration | | Web | Next.js 16 App Router with auth routes, dashboard, workspaces, search, reviews, note detail, prompts/intake/settings, shared clients, E2E specs | | Mobile | Expo app with auth, tabs, note/capture/intake/prompt flows, stores, shared platform clients, MMKV persistence | | Tests present | 67 local test/spec files: 42 backend, 24 web including 9 E2E specs, 10 mobile | | DevOps present | `backend/Dockerfile`, `web/Dockerfile`, `docker-compose.yml`, GitHub Actions CI, docker prep script | | Common platform packages present | `backend-config`, `backend-flags`, `backend-telemetry`, `fastify-auth`, `field-encrypt`, `palace`, `blob-client`, `extraction`, `dashboard-components`, `react-native-platform-sdk`, `sync`, and other `@bytelyst/*` packages exist in common platform | ## Critical Observations - Several older docs are stale. For example, `docs/AGENT_TASK_ROADMAP.md` still says common-platform backend package sources are missing, but those package directories now exist in `learning_ai_common_plat/packages/`. - `docs/ROADMAP.md`, `AGENTS.md`, and older gap docs under `docs/` disagree on implemented status, test counts, endpoints, and platform integration maturity. - Web has many hardcoded hex/RGBA colors and fallback values in app/component styles. This conflicts with the project rule that colors come from `--nl-*` tokens and `@bytelyst/design-tokens`. - Mobile still has hardcoded colors in a few screens, especially provider/intake labels and button text. These should move to `NoteLettTheme` or shared token-derived mappings. - Backend startup still writes Cosmos init messages through `process.stdout` / `process.stderr`; production paths should use `app.log` or shared logger patterns. - Backend config has development-friendly defaults (`DB_PROVIDER=memory`, default JWT secret, telemetry/flags off). Production must fail closed unless explicitly configured. - Web MCP defaults are aligned by P2.1 to the common-platform `mcp-server` on port `4007`. - CI currently covers backend and web lint/typecheck/test/build, and mobile typecheck. It should also run mobile tests, web E2E where practical, Docker builds, and shared platform smoke checks. - The repo uses many common platform packages already, but production readiness should verify runtime behavior against platform-service, extraction-service, blob, telemetry, diagnostics, flags, kill switch, and MCP rather than only checking dependencies. ## Non-Negotiable Implementation Rules - Do not edit `.npmrc` directly. Sync from `../learning_ai_common_plat/scripts/npmrc.template` if needed. - Do not move NoteLett domain logic into common platform unless another product has a concrete reuse need. - Do not add repo-local substitutes for platform concerns already covered by `@bytelyst/*` packages or `platform-service`. - Do not hardcode colors in web or mobile product code. Use `--nl-*` CSS variables, `@bytelyst/design-tokens`, or `NoteLettTheme`. - Do not use `console.log` or raw stdout/stderr in production backend code. Use `req.log`, `app.log`, or shared logger wrappers. - Do not change tests just to pass. Fix the implementation. - Every production Cosmos document must include `productId: PRODUCT_ID`. - Keep every implementation commit small enough to review independently. ## Handoff Prompt For Codex Desktop Use this one-liner to start implementation: ```text In /Users/saravana/BytelystAI/learning_ai_notes, implement docs/PRODUCTION_READINESS_HANDOFF_ROADMAP.md one checklist item at a time. For each item: inspect current code, make the minimal production-quality change, run the listed verification, commit with type(scope): description, push, then update the roadmap row with the commit hash and any verification notes before moving to the next item. Fully leverage /Users/saravana/BytelystAI/learning_ai/learning_ai_common_plat for platform concerns. ``` Use this shorter resume prompt after interruptions: ```text Resume docs/PRODUCTION_READINESS_HANDOFF_ROADMAP.md from the first unchecked item, preserve prior work, commit/push each completed item, and record the commit hash plus verification notes in the roadmap. ``` ## Commit Tracking Format After each completed item, update that checklist row: ```markdown - [x] **P1.1** Task title — Commit: `abc1234`; Verified: `pnpm --filter ...` ``` If an item is intentionally deferred: ```markdown - [ ] **P1.1** Task title — Deferred: reason, owner, date ``` ## Phase P0 — Verify Baseline Before Changes Goal: establish a trustworthy starting point and avoid implementing against stale assumptions. - [x] **P0.1** Run `git status --short` and confirm the worktree is clean or identify user-owned changes. Commit: `fa30191`; Verified: `git status --short --branch` returned `## main...origin/main` with no modified or untracked files before production-readiness edits began. - [x] **P0.2** Run `pnpm install --frozen-lockfile` if dependencies are not installed. Commit: `282bb8d`; Verified: repaired stale common-platform workspace path from `../learning_ai_common_plat/packages/*` to `../learning_ai/learning_ai_common_plat/packages/*`, refreshed `pnpm-lock.yaml` against the local common-platform checkout, then `GITEA_NPM_TOKEN=dummy pnpm install --frozen-lockfile` passed. Notes: pnpm reported existing peer/build-script warnings for common-platform Azure Cosmos/Expo packages; these are baseline observations for later hardening. - [x] **P0.3** Run baseline checks: `pnpm run typecheck`, `pnpm run test`, and `pnpm run build`. Commit: `5c02ce4`; Verified: all three commands were run. Result: baseline failure. `pnpm run typecheck` and `pnpm run build` fail in `@notelett/backend` TypeScript compilation; `pnpm run test` runs 30 backend files and 171 tests successfully, then fails 12 backend suites during collection. Primary failure family: NoteLett Palace/embedding/prompt code is out of sync with the local `learning_ai_common_plat` `@bytelyst/palace` and `@bytelyst/llm` package APIs. Full failure inventory is tracked in P0.5 before fixes. - [x] **P0.4** Run targeted lint checks: `pnpm --filter @notelett/backend run lint`, `pnpm --filter @notelett/web run lint`, and `pnpm --filter @notelett/mobile run lint`. Commit: `b469d16`; Verified: all three lint commands were run independently. Result: baseline failure. Backend lint fails before analysis because `backend/eslint.config.js` cannot resolve `@eslint/js`; web lint reports 16 errors and 4 warnings, mostly React compiler rules (`react-hooks/set-state-in-effect`, `react-hooks/refs`, `react-hooks/immutability`) plus unused-symbol warnings; mobile lint reports 1 error (`import/no-unresolved` for `@bytelyst/billing-client`) and 16 warnings. - [x] **P0.5** Record any baseline failures in this document before fixing them. Commit: `45b16d9`; Verified: baseline failures from P0.2-P0.4 are recorded below before any production-readiness fixes were attempted. Baseline failure inventory from May 5, 2026: | Failure | Evidence | Owner task | | --- | --- | --- | | Dependency install depended on stale local registry behavior before repair | Initial `pnpm install --frozen-lockfile` failed with `ECONNREFUSED` for `http://localhost:3300/api/packages/bytelyst/npm/...`; P0.2 repaired the stale common-platform workspace path and refreshed the lockfile. | P2.2/P8.5 should document and automate local common-platform package resolution and registry expectations. | | Root typecheck fails in backend | `pnpm run typecheck` stops in `@notelett/backend`; errors include missing/changed `@bytelyst/palace` entry/types, removed or changed `@bytelyst/llm` helpers (`embed`, `buildVisionMessage`, `hasVisionContent`), and NoteLett Palace domain types drifting from common-platform package types. | P2.7/P5.4 should align backend AI/Palace integration with current common-platform APIs and add regression tests. | | Root tests fail during backend collection | `pnpm run test` passes 30 backend files and 171 tests, then fails 12 backend suites because Vite cannot resolve `@bytelyst/palace` from Palace/embedding/prompt modules. | P2.7/P5.4 should restore package entry resolution and test coverage for Palace, embeddings, prompts, MCP, and scheduler paths. | | Root build fails in backend | `pnpm run build` fails with the same TypeScript errors as typecheck before web build runs. | P2.7/P5.4 should be complete before final P10 verification. | | Backend lint cannot start | `pnpm --filter @notelett/backend run lint` fails because `backend/eslint.config.js` cannot resolve `@eslint/js`. | P8.1/P8.5 should normalize lint dependencies and CI lint coverage. | | Web lint fails | `pnpm --filter @notelett/web run lint` reports 16 errors and 4 warnings, primarily React compiler lint rules around synchronous effect state updates/ref writes plus several unused symbols. | P4.3/P6.3 should fix interactive/client robustness while preserving behavior. | | Mobile lint fails | `pnpm --filter @notelett/mobile run lint` reports 1 unresolved import error for `@bytelyst/billing-client` and 16 warnings. | P2.5/P7.4/P8.1 should align shared mobile clients and lint coverage. | | Baseline install warnings remain | Frozen install now passes, but pnpm reports common-platform Azure Cosmos peer warnings, Expo/React Native peer warnings, and ignored build scripts for `esbuild`, `sharp`, and `unrs-resolver`. | P2.2/P8.6 should document or resolve dependency health expectations. | Acceptance criteria: - The next implementer knows exactly what passed before production-readiness edits began. - Any baseline failure has a named owner task in a later phase. ## Phase P1 — Documentation Truth Alignment Goal: remove roadmap drift so agents do not chase already completed or obsolete work. - [x] **P1.1** Reconcile `docs/ROADMAP.md` with actual implemented status for product identity, ports, CRUD, tests, Docker, CI, MCP, Smart Actions, intake, Palace, and platform integrations. Commit: `3553b6e`; Verified: `git diff --check`; roadmap now reflects NoteLett identity, port `4016`, implemented CRUD/MCP/review/search/mobile/platform surfaces, and the active May 5 Palace/LLM/lint/smoke blockers instead of March draft checkboxes. - [x] **P1.2** Update `AGENTS.md` with current module list, endpoint list, test counts, route modules, containers, shared packages, and verification commands. Commit: `99efad8`; Verified: `git diff --check`; AGENTS now points at the active production-readiness roadmap, the correct common-platform path, current backend/web/mobile modules, 14 API route modules, expanded endpoint/container lists, current test inventory, lint/E2E commands, and the May 5 baseline failure note. - [x] **P1.3** Update `README.md` quick start with required common-platform services, auth expectations, Docker path, local memory mode, and production env requirements. Commit: `014b098`; Verified: `git diff --check`; README now documents the local common-platform checkout, platform/extraction/MCP ports, memory-mode command, Docker compose smoke path, auth expectations, production env requirements, and current baseline caveat. - [x] **P1.4** Mark stale `docs/GAP_ANALYSIS.md`, `docs/AGENT_TASK_ROADMAP.md`, and older reuse roadmaps as historical or reconcile their open items with this roadmap. Commit: `6307c60`; Verified: `git diff --check`; added historical/superseded banners to the March gap analysis, agent task roadmap, architecture review, and reuse alignment roadmaps, including a correction that the common-platform backend package-source blocker is stale. - [x] **P1.5** Add a concise architecture boundary section: product-local NoteLett logic versus common platform responsibilities. Commit: `8798fdd`; Verified: `git diff --check`; README and AGENTS now document product-local NoteLett domain ownership versus common-platform responsibilities and warn against premature extraction or repo-local platform substitutes. Acceptance criteria: - No active roadmap says common-platform packages are missing when they exist. - `AGENTS.md`, `README.md`, and `docs/ROADMAP.md` agree on current state. - This document remains the active production-readiness checklist. ## Phase P2 — Common Platform Runtime Alignment Goal: prove that NoteLett is using common platform services and packages at runtime, not just declaring dependencies. - [x] **P2.1** Align MCP URL defaults and examples across `web/src/lib/product-config.ts`, `web/.env.example`, settings copy, Docker env, and docs. Use common platform `mcp-server` port `4007` unless the shared server exposes a different production route. Commit: `da3129c`; Verified: legacy MCP port audit returned no matches outside git history, and `rg "NEXT_PUBLIC_MCP_SERVER_URL|MCP_SERVER_URL" web README.md docker-compose.yml docs/PRODUCTION_READINESS_HANDOFF_ROADMAP.md` shows web default/env/settings/Docker now use `http://localhost:4007/api`, matching common-platform `mcp-server` tool routes. - [x] **P2.2** Add or update platform smoke documentation for `platform-service`, `extraction-service`, `mcp-server`, telemetry, diagnostics, flags, kill switch, blob, and NoteLett backend health. Commit: `85b682a`; Verified: `git diff --check`; added `docs/PLATFORM_SMOKE_CHECKS.md` covering common-platform startup, env, unauthenticated health, telemetry, diagnostics, flags/kill switch, blob SAS, extraction, MCP registry/call, NoteLett backend health/bootstrap/diagnostics, and result recording. - [x] **P2.3** Add a local smoke script or extend existing scripts to check `GET /health`, `GET /api/bootstrap`, platform dependencies, and one authenticated product-backend flow in memory mode. Prefer reusing common platform smoke/self-test conventions. Commit: `a2053a7`; Verified: `bash -n scripts/local-smoke.sh`; `GITEA_NPM_TOKEN=dummy pnpm run smoke:local -- --skip-platform`; `git diff --check`. Added `pnpm run smoke:local` with common-platform `@bytelyst/palace`/`@bytelyst/llm` linked-build guards, NoteLett health/bootstrap checks, platform-service/extraction-service/mcp-server health checks, local access-token generation matching shared `@bytelyst/fastify-auth`, and authenticated workspace/note create/read cleanup in `DB_PROVIDER=memory`. Local Docker is unavailable (`docker: command not found`), so the live run skipped platform health while retaining those checks for environments with common-platform services running. - [x] **P2.4** Verify web and mobile shared clients propagate product identity, auth token, and request IDs where supported by common platform clients. Add tests where behavior is local. Commit: `efa2097`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/web exec vitest run src/lib/api-helpers.test.ts src/lib/platform.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/mobile exec vitest run src/api/client.test.ts src/lib/platform-api.test.ts`; `git diff --check`. Added local web/mobile tests proving product API clients send `x-product-id`, bearer auth, and shared `@bytelyst/api-client` request IDs, and proving platform-client wrappers are configured with NoteLett product identity plus auth-token accessors. - [x] **P2.5** Decide whether mobile should adopt `@bytelyst/react-native-platform-sdk` now or explicitly defer it. If adopted, replace redundant local composition; if deferred, document why direct clients are still preferred. Commit: `bea7579`; Verified: `git diff --check`; `rg -n "react-native-platform-sdk|direct shared-client|MOBILE_PLATFORM_SDK_DECISION|Mobile Platform SDK Decision" docs/ROADMAP.md docs/MOBILE_DELEGATION_ROADMAP.md docs/roadmaps/04_MOBILE_ROADMAP.md docs/MOBILE_PLATFORM_SDK_DECISION.md`. Deferred direct SDK adoption in `docs/MOBILE_PLATFORM_SDK_DECISION.md` because current mobile already uses direct shared `@bytelyst/*` clients and the SDK does not yet cover product-backend API calls, blob uploads, diagnostics metadata, billing, feedback, offline queue, or the existing MMKV auth-client token contract without duplicate initialization. - [x] **P2.6** Decide whether `@bytelyst/sync` should back mobile offline/sync behavior now or be deferred. Record the decision and update implementation accordingly. Commit: `f90f358`; Verified: `git diff --check`; `rg -n "@bytelyst/sync|MOBILE_SYNC_DECISION|Mobile Sync Decision|offline-queue" docs/MOBILE_SYNC_DECISION.md docs/MOBILE_DELEGATION_ROADMAP.md docs/roadmaps/04_MOBILE_ROADMAP.md docs/ROADMAP.md`. Deferred `@bytelyst/sync` in `docs/MOBILE_SYNC_DECISION.md` because current mobile uses a narrower `@bytelyst/offline-queue` retry model and full sync adoption needs explicit backend sync windows, tombstones, conflict strategy, MMKV migration, telemetry, and stale/conflict UX. - [x] **P2.7** Evaluate backend LLM usage against `@bytelyst/llm-router`. Either adopt the router for provider/model/fallback governance or document why `@bytelyst/llm` remains sufficient for NoteLett release 1. Commit: `744b938`; Verified: `git diff --check`; `rg -n 'llm-router|BACKEND_LLM_ROUTER_DECISION|Backend LLM Router Decision|@bytelyst/llm' docs/BACKEND_LLM_ROUTER_DECISION.md docs/ROADMAP.md docs/SMART_ACTIONS_ROADMAP.md docs/PRODUCTION_READINESS_HANDOFF_ROADMAP.md`. Deferred router adoption in `docs/BACKEND_LLM_ROUTER_DECISION.md` because release-1 backend needs `@bytelyst/llm` chat, embeddings, mock provider, Azure/OpenAI env compatibility, and multipart vision prompt support; current `@bytelyst/llm-router` is useful future governance but does not yet replace the full `LLMProvider` contract. Acceptance criteria: - Platform-service, extraction-service, blob, telemetry, diagnostics, flags, kill switch, and MCP are documented with real smoke steps. - Shared package usage has no unnecessary product-local duplicate for platform concerns. - Deferrals are explicit, not implied by absence. ## Phase P3 — Security And Production Config Hardening Goal: production starts fail closed and do not silently run with development-only security. - [x] **P3.1** Change backend config validation so production cannot use default `JWT_SECRET`, memory DB, disabled encryption by accident, or missing Cosmos credentials. Keep test/dev ergonomics intact. Commit: `e7d381f`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `node --import tsx --input-type=module -e "import { parseConfig } from './src/lib/config.ts'; ..."` accepted a safe production env and rejected unsafe defaults; `git diff --check`. Added exported `parseConfig()`, safe string boolean parsing, development JWT defaulting only for non-production ergonomics, production rejection for default/short JWT secrets, memory DB, missing Cosmos endpoint/key/database, disabled field encryption, and production memory key provider. - [x] **P3.2** Add tests for production config validation: missing secret, default secret, memory DB in production, missing Cosmos env, encryption provider requirements. Commit: `8007fac`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend exec vitest run src/lib/config.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `git diff --check`. Added 11 focused config tests covering dev ergonomics, safe production config, missing/default/short production JWTs, memory DB rejection, missing Cosmos env, disabled encryption, production memory provider rejection, env key requirements, and AKV URL requirements. - [x] **P3.3** Review all unauthenticated backend endpoints. Keep `/health`, `/api/bootstrap`, and public share reads intentional; protect diagnostics or make them explicitly dev/admin-gated. Commit: `56a051a`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend exec vitest run src/diagnostics.test.ts src/server.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `git diff --check`. Kept `/health`, `/api/bootstrap`, and `/api/public/note-shares/:token` intentionally public; moved diagnostics into `diagnosticsRoutes()` with dev/test open access and production `admin`/`owner` auth via shared auth middleware. - [x] **P3.4** Add or verify rate limiting and abuse controls for public share, auth-facing, prompt, intake, and LLM-backed endpoints using shared platform patterns where available. Commit: `ee4a8ab`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend exec vitest run src/lib/rate-limit.test.ts src/modules/notes/copilot.test.ts src/modules/note-prompts/note-prompts.test.ts src/modules/intake/routes.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `git diff --check`. Added a shared in-memory sliding-window limiter backed by `TooManyRequestsError` from `@bytelyst/errors`, replaced the local intake limiter, and guarded public share reads plus prompt, Smart Action, embedding, URL extraction, merge/compare, copilot, title suggestion, and workspace chat endpoints before expensive work begins. Auth-facing endpoints remain owned by common-platform `platform-service`. - [x] **P3.5** Verify agent write paths enforce role, product scope, workspace membership, idempotency, dry-run, and audit trail behavior. Add missing tests. Commit: `8a53dfd`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend exec vitest run src/mcp/note-tools.test.ts src/mcp/register-note-tools.test.ts src/modules/note-agent-actions/routes.test.ts src/modules/note-agent-actions/routes.integration.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `git diff --check`. Added defense-in-depth role checks inside executable MCP tools, user/product/workspace/note scope guards before writes, duplicate idempotency-key rejection before side effects, and regression coverage for dry-run no-persistence, audit metadata, scoped updates, create-draft workspace ownership, and link-note product scope. - [x] **P3.6** Verify field encryption coverage for note body, sensitive artifact metadata, prompt content if needed, and agent action details. Add migration notes if fields are newly encrypted. Commit: `6b37b72`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend exec vitest run src/lib/encryption-coverage.test.ts src/modules/note-artifacts/routes.integration.test.ts src/modules/note-prompts/note-prompts.test.ts src/modules/note-agent-actions/routes.integration.test.ts`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/backend run typecheck`; `git diff --check`. Added shared optional text-field encryption helpers; encrypted artifact `title`, `description`, and `blobPath`; prompt `description`, `systemPrompt`, and `userPromptTemplate`; and agent action `reason`, `beforeSummary`, `afterSummary`, and `reviewNote`, while preserving plaintext query metadata and backward-compatible plaintext reads. Added raw-storage encryption tests and migration notes in `docs/FIELD_ENCRYPTION_COVERAGE.md`. Acceptance criteria: - A production container cannot start with unsafe defaults. - Public and diagnostic surfaces are intentionally scoped. - Agent and AI write paths are auditable and permissioned. ## Phase P4 — Design System And Accessibility Compliance Goal: remove local visual drift and meet interaction accessibility expectations. - [x] **P4.1** Replace web hardcoded hex/RGBA colors and token fallbacks in `web/src/app` and `web/src/components` with `--nl-*` tokens or shared component primitives. Commit: `6ede2be`; Verified: `rg -n "#[0-9a-fA-F]{3,8}|rgba?\\(|var\\(--nl-[^)]+," web/src/app web/src/components --glob '!**/*.test.*'` returned no matches; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/web run test`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/web run typecheck` after building linked `@bytelyst/billing-client`; `git diff --check`. Added semantic CSS token aliases for overlays, translucent surfaces, muted accent/status backgrounds, and command shadows, then replaced app/component hex, RGB/RGBA, and `--nl-*` fallback values with tokens. - [x] **P4.2** Replace mobile hardcoded colors in screens and label mappings with `NoteLettTheme`, token-derived values, or named semantic mappings. Commit: `6bbbac8`; Verified: `rg -n "#[0-9a-fA-F]{3,8}|rgba?\\(" mobile/src --glob '!**/*.test.*'` returned no matches; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/mobile run typecheck`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/mobile run test`; `git diff --check`. Added token-derived `colors.onAccent` and `colors.overlayBackdrop`, replaced white button text and modal overlay literals, and moved intake content-type badge colors to semantic theme mappings. - [x] **P4.3** Audit web interactive elements for visible text or `aria-label`, including icon buttons, modals, editor controls, review actions, prompt actions, and settings. Commit: `01c2d31`; Verified: `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/web run typecheck`; `GITEA_NPM_TOKEN=dummy pnpm --filter @notelett/web run test`; `rg -n "]*>\\s*(×|✕|×|