Commit Graph

538 Commits

Author SHA1 Message Date
saravanakumardb1
131b73cfc1 fix(web): repair Next.js standalone static-chunks 404 in Docker + harden 2 e2e specs
Root cause of bug: web Dockerfile copied .next/static to the wrong path
in the runtime stage. The Next.js 16 standalone server (CMD 'node
web/server.js' from /app/web) runs from /app/web/web/server.js because
'standalone' wraps the source directory. It serves /_next/static/* from
'./web/.next/static' (relative to the standalone server's location),
not from './.next/static' (which is what the previous COPY produced).

Symptom: in the deployed Docker stack at http://localhost:3050 every
client-side JS chunk under /_next/static/chunks/* returned HTTP 404
with content-type text/plain. The browser refused to execute the
chunks (strict MIME), so the SPA never hydrated. All Playwright tests
that ask for any dynamic UI text on a (app)/ page would time out
because AuthGuard never ran in the browser.

Discovery path: deployed compose stack via 'docker compose up -d
--build' + 'scripts/e2e-docker-test.sh' (backend API 9/9 ✓), then ran
Playwright against NOTELETT_WEB_PORT=3050. settings.spec failed with
'product configuration section' not visible. Page snapshot showed
just <skip-to-content link> + toast region — no other content. Console
logs revealed every /_next/static/chunks/* was 404 with text/plain.
'docker exec ls' showed BUILD_ID at /app/web/web/.next/BUILD_ID and
static at /app/web/.next/static — wrong path. Moved static into the
standalone tree and chunks now serve 200 with application/javascript.

Fix:
  web/Dockerfile: change
    COPY --from=builder /app/web/.next/static ./.next/static
  to
    COPY --from=builder /app/web/.next/static ./web/.next/static
  with explanatory comment so this doesn't regress.

Test hardening (these tests were dev-server-only by accident — they
worked locally because Next.js dev did not enforce the same static
path layout; the bug above hid them in production builds too):

  web/e2e/accessibility.spec.ts — 'focus-visible ring appears on tab
  navigation' was navigating to /dashboard which AuthGuard correctly
  redirects when unauthenticated, leaving the DOM empty (AuthGuard
  returns null until verifySessionAndReadiness completes) so Tab
  presses focused nothing. Switched to /login which is unauthenticated
  by design and has known focusable form inputs.

  web/e2e/settings.spec.ts — 'shows product configuration section'
  expected /settings to render content without auth. Now obtains real
  tokens from platform-service via API, seeds them via addInitScript,
  and falls back to test.skip with a clear message if platform-service
  is not reachable.

Verified:
  - All 31 Playwright tests across navigation/accessibility/dashboard/
    search/settings/smart-actions/reviews specs PASS against the
    deployed Docker stack at :3050.
  - 'pnpm run verify': backend 380/380, web 96/96, mobile 97/97.
  - 'bash scripts/e2e-docker-test.sh': 9/9 backend API CRUD steps pass.
  - 'curl -sI http://localhost:3050/_next/static/chunks/app/error-*.js'
    now returns 200 + application/javascript.

Not migrated: e2e/release-flows.spec.ts and e2e/visual-regression.spec.ts
intentionally remain dev-server-targeted. release-flows.spec uses
page.route() to mock backend responses and is meant to test the UI in
isolation against a dev server. visual-regression.spec needs baseline
regeneration after the UI5-UI8 migration; this is a separate workstream
tracked in docs/UI_UX_PLATFORM_CORE_ROADMAP.md.
2026-05-23 02:29:40 -07:00
saravanakumardb1
82ce90f91d docs(roadmap): mark sprints 3-6 complete; record post-Sprint-B UI migration
Sprint 3 (UI5), Sprint 4 (UI6), Sprint 5 (UI7+UI8), Sprint 6 (production
hardening) are all now substantially complete as of May 23, 2026.

NEXT_SPRINT_ROADMAP.md:
- Sprint 3 UI5 marked complete with commit hashes 9c65899, 30a30ce, 2408f43.
- Sprint 4 UI6 marked complete with commit 8d484c3.
- Sprint 5 UI7+UI8 marked complete with commits 2408f43, 3288e28, 0c982de.
- Sprint 6 production hardening verified item-by-item:
    backend config fail-closed: already enforced via lib/config.ts superRefine.
    backend logging clean: grep returns zero matches outside tests.
    CI mobile + web E2E + Docker build: jobs already wired.
    Docker compose smoke: scripts/e2e-docker-test.sh validated locally.
    Shared service smoke: backend+web verified at :3050/:4016 with sibling
    platform-service (4003), extraction-service (4005), mcp-server (4007).
    Only stale doc cleanup remains as deferred non-blocking work.

PRODUCTION_READINESS_HANDOFF_ROADMAP.md:
- Replaced Sprint-A 'Docker compose smoke deferred' note with verified
  status: Dockerfile corporate-proxy workarounds added, override pins
  sibling-service URLs and aligns JWT secret, e2e-docker-test.sh
  exercises full login/CORS/CRUD lifecycle.
- Added 'Post-Sprint-B UI Migration' section documenting the four UI
  ratchet category counts (legacy/colors/imports all 0; raw controls 14
  intentional) and the UI drift ratchet as the canonical CI gate.

No code changes.
2026-05-23 02:04:09 -07:00
saravanakumardb1
0c982de7e6 feat(web/ui8): remove legacy global classes + tighten audit regex + lock CI gate
UI8 closes the migration cycle started by UI0. The four legacy global
classes (.surface-card, .surface-muted, .badge, .input-shell) are
removed from web/src/app/globals.css and the CI ratchet now enforces
zero new occurrences across three of the four drift categories.

Changes:

1. Audit regex precision (scripts/ui-drift-audit.sh, scripts/ui-drift-ratchet.sh)

   The previous pattern 'className="[^"]*(badge|surface-card|surface-muted|input-shell)'
   matched the literal token anywhere inside className, which caused 21
   false positives against Tailwind arbitrary values like
   'bg-[color:var(--nl-surface-muted)]' where the legacy name appears
   inside a 'var(--nl-...)' reference.

   New pattern requires the legacy class to be a whole class token —
   either at the start of className, or preceded by a space, and
   followed by a space or closing quote. Result: 21 false positives
   eliminated; the ratchet now reports an honest 0 for the legacy
   category.

2. globals.css cleanup (web/src/app/globals.css)

   Removed .surface-card, .surface-muted, .badge, .input-shell rules.
   Only truly global utilities remain (typography, focus-visible,
   sr-only, skip-link, motion preferences, layout grids). A header
   comment documents that re-introductions should be solved at the
   call-site with a primitive, not by restoring the global rule.

3. Ratchet baseline (scripts/ui-drift-baseline.json)

   Final counts after UI5–UI8 across the session:
     raw interactive controls       14   (was 38 at start)
     legacy global surface classes  0    (was 92 at start)
     hardcoded color literals       0    (no change, was already 0)
     direct @bytelyst/ui imports    0    (no change, was already 0)

   The 14 remaining raw controls are intentional and tracked:
     NoteEditor toolbar buttons (10)
     ArtifactPanel hidden file input (1)
     search/page radio inputs (2)
     NoteVersionsPanel disclosure button (1)

4. CI gate (.github/workflows/ci.yml release-guards job)

   Documented that the ratchet is the canonical gate post-UI8: because
   legacy/colors/imports baselines are 0, any new occurrence in those
   three categories now fails CI. The strict-audit script is kept as
   a local diagnostic tool but not wired as a gate (would fail on the
   14 intentional raw controls).

5. Roadmap (docs/UI_UX_PLATFORM_CORE_ROADMAP.md)

   Marked UI5, UI6, UI7, UI8 all complete with per-phase commit hashes
   and explicit deliverables.

Cumulative migration impact (from initial baseline):
   raw interactive controls       38 → 14   (-24, -63%)
   legacy global surface classes  92 → 0    (-92, -100%)

Verified:
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97
- bash scripts/ui-drift-ratchet.sh: all four categories at baseline
- bash scripts/ui-drift-audit.sh: only "Raw interactive controls"
  category has matches (intentional, tracked above)
- Live Docker stack at http://localhost:3050 still serves 200,
  backend health 200
2026-05-23 01:55:36 -07:00
saravanakumardb1
3288e28f5c feat(web/ui7): migrate note detail, palace, gaps/prompts pages, broadcast banner
Phase UI7 — completes the note detail surface, the Palace knowledge
exploration page + its panels, the knowledge-gaps page, the prompts
page empty states, and the broadcast banner. Brings the ratchet down
to 14 raw controls / 21 legacy class matches — both genuine remaining
intentional items (NoteEditor toolbar, hidden file input, audit false
positives matching Tailwind arbitrary values).

notes/[noteId]/page.tsx:
- 'Loading' badge → Badge variant=neutral.
- Loading/error sections → Card.
- Review-state link → Link wrapping Badge.

palace/page.tsx:
- Wing <select> → Select with options=[{value,label}].

palace components:
- PalacePanel.tsx — search input → Input, hall chip → Badge.
- MemoryTimeline.tsx — hall chip → Badge.
- KnowledgeGraphView.tsx — entity query input → Input.

workspaces/[id]/gaps/page.tsx:
- Topic Coverage section → Card, chip → Badge.
- Empty-state + per-gap items → Card.

prompts/page.tsx:
- Loading + empty-state divs → Card.

landing page (/):
- section.surface-card → Card.
- 'Backend-backed web surface' badge → Badge.
- 'Open dashboard'/'Browse workspaces' links → utility classes.

share/[token]/page.tsx:
- Read-only public share badge → Badge.
- Main content surface-card + input-shell body wrapper → Card with
  bordered body container.

BroadcastBanner.tsx:
- CTA + Dismiss raw <button> → Button (ghost variant, size sm).

Cumulative ratchet impact since session start:
  raw interactive controls       38 → 14   (-24)
  legacy global surface classes  92 → 21   (-71)
  hardcoded color literals       0           (clean)
  direct @bytelyst/ui imports    0           (clean)

Verified: pnpm typecheck, test (96/96), ratchet at new baseline.
2026-05-23 01:49:15 -07:00
saravanakumardb1
8d484c30d1 feat(web/ui6): migrate dashboard, workspaces, search, chat pages
Phase UI6 — the three highest-traffic operator surfaces plus chat
move off legacy globals onto @bytelyst/ui Card + Badge + Input +
Select + Textarea + Button primitives via the local adapter.

dashboard/page.tsx:
- Welcome card, Saved views card, Quick links card, Operator workflows
  card, Recent note activity card — all section.surface-card → Card.
- All saved-view/quick-link/workflow/note rows: surface-muted with
  inline styles → grid+rounded+bg utility classes with hover state.
- All inline 'badge' spans (scope, status, tags) → Badge with
  semantic variants (workflow status maps to warning/success).

workspaces/page.tsx:
- Saved-views aside, filter section, workspace article rows, error
  banner — all surface-card → Card.
- Filter input → Input. Visibility/owner/tag chips → Badge.
- Workspace-note rows → utility-class hover panels.

search/page.tsx:
- POST /notes/search action chip → Badge.
- Saved searches aside + results pane — both surface-card → Card.
- '+ Save current' button + per-view Remove button — raw <button>
  → Button (size sm, ghost variant for Remove).
- Search input + filter chips + result rows — Input + Badge +
  utility-class panels.

chat/page.tsx:
- Workspace <select> → Select with options=[{value,label}].
- Question <textarea> → Textarea.

Ratchet impact for this commit:
  raw interactive controls       25 → 19  (-6)
  legacy global surface classes  67 → 38  (-29)

Cumulative since session start (38/92 baseline):
  raw   38 → 19  (-19)
  legacy 92 → 38  (-54)

Verified: pnpm typecheck, test (96/96), audit:ui:ratchet at new
baseline.
2026-05-23 01:38:35 -07:00
saravanakumardb1
2408f43426 feat(web/ui5+ui7): migrate 12 components to @bytelyst/ui primitives
Finishes UI5 and kicks off UI7 by migrating the remaining form-heavy
components plus the note-detail right-rail panels. Drops legacy class
matches from 92 → 67 (-25) and raw interactive controls from 38 → 25
(-13). Ratchet baseline updated to the new floor.

Components migrated:

UI5 finish:
- NoteEditor.tsx — surface-card wrapper → Card, title input → Input,
  Tiptap editor className updated to use border + bg classes instead
  of input-shell. Toolbar buttons left as raw (intentional, tightly
  styled icon controls).
- SmartActionsPanel.tsx — result panel surface-muted → Tailwind
  bg-[var(--nl-surface-muted)] utility.
- ArtifactPanel.tsx — section→Card, badge→Badge, all three input-shell
  inputs/selects/textareas→Input/Select/Textarea, surface-muted form
  shell + per-artifact row → Tailwind bg-utility, raw <button> Open
  → Button.
- CommandPalette.tsx — surface-card command sheet → Tailwind layered
  classes, search input → Input (now ref-forwarded), kind badge → Badge.

UI7 component pass:
- MetadataPanel.tsx — section→Card, tag badge→Badge.
- LinkedNotesPanel.tsx — section→Card, surface-muted link row →
  Tailwind bg-utility with hover state.
- PalaceStats.tsx — section→Card, inline styles → Tailwind utilities.
- ExtractedTasksPanel.tsx — surface-muted row → Tailwind.
- NoteVersionsPanel.tsx — all three section/surface-card variants →
  Card + raw button → preserved (interactive disclosure).
- Pagination.tsx — raw <button> Previous/Next → Button, surface-muted
  → built-in secondary variant.
- TaskReviewPanel.tsx — full migration: section→Card, badge→Badge,
  input-shell + textarea + raw button → Input/Textarea/Button.
- SurveyBanner.tsx — survey answer input-shell → Input.

Adapter changes:
- web/src/components/ui/Primitives.tsx — Input and Textarea now use
  React.forwardRef so callers like CommandPalette can attach refs.

Verified:
- pnpm --filter @notelett/web run typecheck: passes
- pnpm --filter @notelett/web test: 96/96 still pass
- pnpm run audit:ui:ratchet: at new baseline (25/67/0/0)
- pnpm run audit:ui: legacy class matches now in dashboard / search /
  workspaces / notes-detail / palace / chat pages (UI6/UI7 page targets)
2026-05-23 01:33:48 -07:00
saravanakumardb1
d5e857dbf7 test(e2e): docker compose E2E test + seed scripts + 9-step verification
Implements the full E2E flow against the deployed docker stack and
documents it as a repeatable test playbook.

Surfaced and fixed three real issues while building the E2E:

1. JWT secret mismatch — docker-compose.override.yml backend was using
   a NoteLett-only JWT_SECRET that platform-service did not share, so
   every Authorization: Bearer call returned 'Invalid or expired token'.
   Aligned the override to use platform-service's actual secret
   (dev-ecosystem-secret-do-not-use-in-production).

2. CORS preflight missing PATCH/DELETE — @bytelyst/fastify-core registers
   @fastify/cors with only { origin }, which leaves Access-Control-Allow-
   Methods at the @fastify/cors default of 'GET,HEAD,POST'. Real browser
   PATCH/DELETE preflights would fail. Added an onSend hook in
   backend/src/server.ts that rewrites the header to
   'GET,HEAD,POST,PATCH,PUT,DELETE,OPTIONS' on CORS preflight responses.

3. Product 'notelett' wasn't registered with platform-service — auth
   register/login both error with 'Unknown or disabled product: notelett'.
   The seed script now POSTs to /api/products idempotently.

Deliverables:

- scripts/e2e-docker-seed.sh — idempotent: registers the notelett product
  and creates two test users (admin@notelett.app with role=admin who can
  write, user@notelett.app with role=user who is read-only). Re-runs are
  no-ops once seeded.

- scripts/e2e-docker-test.sh — 9-step E2E that drives the deployed stack
  via HTTP only (no browser): login → CORS preflight for PATCH →
  workspace create → note create → note read → note PATCH (status:
  draft→active) → note list → note delete → workspace delete.

- docs/testing/E2E_DOCKER_TESTING.md — full playbook covering prereqs,
  seed, automated E2E, manual UI smoke, stack architecture diagram,
  troubleshooting (JWT mismatch, unknown product, role rejection,
  CORS, port conflict, data loss), tear-down, CI wiring guidance.

- package.json — pnpm e2e:docker:seed and pnpm e2e:docker:test
  shortcuts.

Verified live on this host's deployed stack:

  $ bash scripts/e2e-docker-seed.sh
  ↷ product 'notelett' already exists
  ↷ admin user already registered + login works
  ✓ user created
  🟢 Seed complete.

  $ bash scripts/e2e-docker-test.sh
  ✓ user=usr_e094e0c2-... role=admin
  ✓ CORS allows PATCH
  ✓ workspace created
  ✓ note created
  ✓ note read matches
  ✓ note patched (status: draft → active)
  ✓ note list returned (1 item)
  ✓ note deleted (HTTP 204)
  ✓ workspace deleted (HTTP 204)
  🟢 All 9 E2E steps passed.

Backend regression suite still green: 380/380.
2026-05-23 01:16:19 -07:00
saravanakumardb1
e5221afb87 feat(deploy): backend Docker corp-proxy support + local compose override
Two changes that make 'docker compose up' actually work on this host
(and on any corporate network with TLS interception of npmjs.org):

1. backend/Dockerfile gains the same NODE_TLS_REJECT_UNAUTHORIZED=0 +
   NPM_CONFIG_STRICT_SSL=false envs and 'npm config set strict-ssl false'
   step that web/Dockerfile already had. Without this, the 'npm install
   -g pnpm@10.6.5' step failed with UNABLE_TO_GET_ISSUER_CERT_LOCALLY
   on corp networks. Build-time-only; production runtime image is
   unaffected.

2. docker-compose.override.yml (new) is picked up automatically by
   'docker compose up' and:
   - remaps the web container's host port from 3000 to 3050 (port 3000
     on this host is held by Grafana). Uses 'ports: !override' so the
     base port mapping is replaced rather than appended.
   - points the backend at the sibling platform-service (4003),
     extraction-service (4005), and mcp-server (4007) running on the
     host network via host.docker.internal.
   - sets DB_PROVIDER=memory and a 32+ char JWT_SECRET so the backend
     starts in dev mode without Cosmos credentials.

Verified live on this host:
  docker compose up -d → both notelett-backend (healthy) and
  notelett-web running.
  curl http://localhost:4016/health → {status:ok,service:notelett-backend}
  curl http://localhost:3050/dashboard → HTTP 200, '<title>NoteLett</title>'
2026-05-23 01:04:18 -07:00
saravanakumardb1
7103660c95 test(e2e): fix 4 pre-existing E2E failures and make port-conflict-proof
Audit of the full E2E suite (43 specs) surfaced four issues that were
hiding behind 'all 96/96 web unit tests pass' but actually meant the
browser-level coverage was broken end-to-end. All four are fixed and
the suite now passes 43/43.

1. Port conflict silently testing wrong app. playwright.config.ts hard-
   coded baseURL=http://localhost:3000 with reuseExistingServer:true on
   non-CI hosts. When the dev host had ANY service on :3000 (Grafana,
   chronomind, etc), Playwright happily ran the entire E2E suite
   against the wrong app and reported the unrelated failures as
   'real'. Now honors NOTELETT_WEB_PORT env (default 3000) so a
   contributor can opt into any free port and Playwright drives both
   baseURL and the dev-server PORT consistently.

2. Missing test dependency. web/e2e/accessibility.spec.ts imports
   @axe-core/playwright but web/package.json never declared it.
   The accessibility coverage was DOA — every CI run that included
   this spec would module-not-found-error before a single check ran.
   Added @axe-core/playwright to devDependencies.

3. Mock that never fires. smart-actions.spec.ts 'history API mock
   returns items' used page.route() to mock /api/note-prompts/history
   then bypassed the mock entirely with page.request.get() (which uses
   Playwright's separate request context, not the browser context that
   page.route intercepts). The request went to the dev server and got
   404. Replaced with page.goto + page.evaluate(fetch(...)) so the
   browser-side fetch hits the page.route mock as intended.

4. Missing visual-regression baselines. visual-regression.spec.ts had
   no committed baseline screenshots for dashboard / workspaces /
   search. First run on a clean host always reported 'snapshot doesn't
   exist, writing actual'. Generated and committed darwin baselines.

Verified end-to-end (NOTELETT_WEB_PORT=3050 against this host's free
port):
  43 passed (34.8s)

Total test-tier counts on main now:
  backend unit + integration (memory)   380/380
  backend cosmos emulator (live)        4/4
  web vitest                            96/96
  mobile vitest                         97/97
  web playwright e2e                    43/43
  ---
  TOTAL                                 620/620
2026-05-23 00:50:29 -07:00
saravanakumardb1
34cb219962 docs(p10.5): live shared-service smoke verified end-to-end May 23, 2026
Previously P10.5 was marked complete with a deferral note because the
sibling services (platform-service 4003, extraction-service 4005,
mcp-server 4007) were not running on the audit host. Today they are
all running, so I executed the smoke and confirmed it passes.

Command:
  JWT_SECRET="dev-secret-change-me-at-least-32-characters-long" \
    bash scripts/local-smoke.sh

Output (exit 0, 11 ok lines):
  info: starting NoteLett backend in memory mode
  ok: NoteLett backend started at http://localhost:4016
  ok: NoteLett health
  ok: NoteLett bootstrap
  ok: platform-service health
  ok: extraction-service health
  ok: mcp-server health
  ok: authenticated workspace create
  ok: authenticated note create
  ok: authenticated note read
  ok: smoke cleanup attempted
  ok: local production-readiness smoke passed

Updates:
- §Post-Sprint-A Re-verification: replaces the blanket deferral note
  with the actual verification details for live shared-service smoke
  and a separate, narrower deferral note for Docker compose smoke
  (which still fails on corp-network hosts due to TLS interception in
  the backend/Dockerfile npm install step but succeeds on CI).
- §P10.5: replaces the historical deferral text with today's
  end-to-end verification result.
2026-05-23 00:30:44 -07:00
saravanakumardb1
79e936bd68 feat(ci): Cosmos emulator smoke job exercises partition-key paths
The existing 380-test backend suite runs entirely against the in-memory
datastore provider, which treats every partition-key value as equivalent.
This hid one entire class of bug — partition-key mismatches — until
production. D7 closes that gap.

Implementation:

- backend/src/test-helpers.ts adds useCosmosDatastore() that swaps the
  active provider for CosmosDatastoreProvider using COSMOS_ENDPOINT /
  COSMOS_KEY / COSMOS_DATABASE. Throws synchronously when env is missing
  so a misconfigured run fails loudly instead of silently falling back
  to in-memory.

- backend/vitest.config.ts now excludes src/**/*.cosmos.test.ts so the
  default 'pnpm test' run stays green for contributors without Docker.

- backend/vitest.cosmos.config.ts (new) includes ONLY *.cosmos.test.ts,
  bumps testTimeout to 30s / hookTimeout to 60s for the real client
  round-trips, and locks DB_PROVIDER=cosmos in test env.

- backend/src/cosmos.smoke.cosmos.test.ts (new) covers the four most
  important partition-key contracts in NoteLett:
    workspaces      /userId
    notes           /workspaceId
    note_tasks      /workspaceId
    note_shares     /workspaceId  (full create → resolve → delete → null)
  Each test also asserts that a wrong-partition-key lookup returns null,
  which is the failure mode the in-memory provider cannot simulate.

- backend/package.json adds 'test:cosmos' script.

- .github/workflows/ci.yml gains a backend-cosmos job that boots the
  official mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator
  container as a service, waits for it to be ready (60 × 5s polls of
  /_explorer/emulator.pem), then runs pnpm test:cosmos against it.
  The job depends on the existing backend job so the emulator only
  spins up after unit tests pass.

Verified locally:
- pnpm --filter @notelett/backend test: 380/380 (cosmos suite excluded)
- vitest list --config vitest.cosmos.config.ts: 4 tests under the cosmos
  smoke suite, as designed
- pnpm run verify: end-to-end green (backend 380/380, web 96/96,
  mobile 97/97)
- ci.yml passes Python yaml.safe_load

CI verification: the new job will execute on the next push. Local
verification against the emulator requires Docker on the dev host.
2026-05-23 00:25:24 -07:00
saravanakumardb1
3c4d46f3ad fix(web): resolve the 5 actionable React-compiler lint advisories
Web lint warnings reduced from 20 → 15 by fixing the categories that
flag real architectural smells rather than the canonical
fetch-on-mount setState pattern.

Real fixes:

1. web/src/lib/use-theme.ts — replace useEffect + setState mount-sync
   pattern with React.useSyncExternalStore. The hook now subscribes to
   browser storage events, returns a stable snapshot for SSR, and uses
   a manual storage-event dispatch so same-document setters refresh
   correctly. Eliminates the cascading-render advisory and gains free
   cross-tab theme sync.

2. web/src/lib/use-keyboard-shortcuts.ts — move ref assignment from
   render time into a useEffect. Fixes the 'Cannot access refs during
   render' advisory without behavior change.

3. web/src/components/NoteEditor.tsx — move onSaveRef.current = onSave
   from render time into a useEffect for the same reason.

4. web/src/app/(app)/reviews/page.tsx — wrap handleDecision and
   handleBatchDecision in useCallback so the useEffect that depends
   on them no longer re-subscribes the keydown listener on every
   render. Fixes both react-hooks/exhaustive-deps warnings and the
   underlying perf bug they pointed at.

5. web/src/app/(app)/prompts/page.tsx — wrap loadTemplates in
   useCallback declared before the useEffect that calls it. Fixes
   the 'Cannot access variable before it is declared' advisory.

Remaining 15 warnings are React-compiler runtime hints about
fetchData().then(setData) patterns inside useEffect, which is the
canonical fetch-on-mount pattern shown in React's own docs. Resolving
them properly requires Suspense + use() or risky startTransition
wraps; both are out of scope and tracked under future tech debt.

Verified:
- pnpm --filter @notelett/web run typecheck: passes
- pnpm --filter @notelett/web run lint: 0 errors, 15 warnings (down 5)
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97
2026-05-23 00:20:02 -07:00
saravanakumardb1
78433b0e45 feat(ci): one-way UI drift ratchet to prevent regressions
UI8 deferred deleting the legacy global classes (.surface-card,
.surface-muted, .input-shell, .badge) because 69+ call sites in UI6/UI7
territory (dashboard, search, workspaces, notes detail, chat, palace)
still depend on them. Removing the globals before those screens migrate
would visually break the app.

Instead, ship a one-way ratchet that solves the actually-important
problem: prevent NEW legacy usage from creeping in while existing
sites get migrated.

- scripts/ui-drift-ratchet.sh — reads scripts/ui-drift-baseline.json
  and FAILS if any of the four UI drift categories regress above the
  tracked baseline. Pure bash, no jq required, works with grep or
  ripgrep. Uses the same patterns as scripts/ui-drift-audit.sh.
- scripts/ui-drift-baseline.json — checked-in baseline captured today:
  raw controls 38, legacy classes 92, hardcoded colors 0, direct imports 0.
- package.json — adds pnpm run audit:ui:ratchet and
  audit:ui:ratchet:update scripts.
- .github/workflows/ci.yml release-guards job — runs the ratchet as a
  required step plus the existing audit in report mode.
- docs/UI_UX_PLATFORM_CORE_ROADMAP.md — marks the CI-guard checklist
  item complete, documents the path to fully strict mode (drive
  baseline to zero, then delete globals.css legacy classes, then flip
  audit:ui:strict from advisory to required).

Verified:
- Ratchet at baseline: exits 0
- Synthetic regression (added a file with surface-card + raw <input>):
  ratchet correctly exits 1, reporting +1 in each affected category
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97 (no
  behavior change)
2026-05-23 00:13:50 -07:00
saravanakumardb1
30a30ceb0f feat(web/ui5): migrate settings page + 4 modals to @bytelyst/ui primitives
Completes the high-leverage half of UI5 by migrating the most form-heavy
authenticated screens off the legacy 'input-shell' / inline-style pattern
onto Input, Textarea, Select, and AlertBanner primitives.

Migrated:
- web/src/app/(app)/settings/page.tsx — change-password form, feedback
  form, MCP/API-tokens/offline-queue cards. Replaces 'surface-card'
  sections with Card components, 'input-shell' inputs/selects/textareas
  with Input/Select/Textarea, and inline error/success divs with
  AlertBanner.
- web/src/components/CreateNoteModal.tsx — template/workspace/title/body/tags
  fields. Select primitive uses options=[{value,label}].
- web/src/components/LinkNoteModal.tsx — search input + relationship-type
  select + alert banner for errors.
- web/src/components/ShareDialog.tsx — user-id input, permission select,
  collaborator/public-link rows now use AlertBanner (tone='neutral') for
  the muted-surface look. Web Share API unsupported message is now a
  proper tone='warning' banner.
- web/src/components/PromptTemplateEditor.tsx — full form (name, slug,
  description, 3 selects, 2 textareas) migrated.

All existing tests continue to pass without modification because
@testing-library queries (getByLabel, getByPlaceholder, getByText) are
robust against the underlying HTML structure changes.

Verified:
- pnpm --filter @notelett/web run typecheck: passes
- pnpm --filter @notelett/web run test: 96/96 (existing CreateNoteModal,
  LinkNoteModal, ShareDialog suites all green)
- pnpm run verify: end-to-end (backend 380/380, web 96/96, mobile 97/97)
- Legacy class matches in web/src dropped from 89 to 69 over the UI5
  slice; remaining matches are in UI6/UI7 territory (dashboard, search,
  workspaces list, notes detail, chat, palace, NoteEditor).
2026-05-23 00:05:49 -07:00
saravanakumardb1
a83e60a60a fix(workspace): pin React and React-DOM to a single version via pnpm overrides
While migrating CreateNoteModal to use @bytelyst/ui Input/Select/Textarea
(which internally call React.useId), Vitest tests failed with:

  TypeError: Cannot read properties of null (reading 'useId')

Root cause: the web package pins react@19.2.0 but @bytelyst/ui declared
react: '^19.0.0' as a peer, so pnpm resolved 19.2.6 for it from the
common-platform side. Two React copies coexisted (19.2.0 and 19.2.6),
the @bytelyst/ui components linked against one and react-dom test-rendered
against the other, and useId failed because the dispatcher belonged to
a different React instance than the consumer.

Fix: declare pnpm.overrides in the workspace root so the entire monorepo
resolves to a single react@19.2.0 / react-dom@19.2.0 pair. Verified via
'pnpm why react' (all transitive references now point at 19.2.0) and the
on-disk symlinks (web/node_modules/@bytelyst/ui/node_modules/react and
common-plat/packages/ui/node_modules/react both link to
.pnpm/react@19.2.0).
2026-05-23 00:05:31 -07:00
saravanakumardb1
f4564d7cd6 chore(web): remove dead code surfaced by lint
Three mechanical lint warnings in the web package are resolved with
zero behavior change:

- web/src/app/(app)/notes/[noteId]/page.tsx — rename onTagsAccepted
  callback param to '_tags' to match the no-unused-vars allowlist
  (the param is intentionally unused; we trigger a re-save regardless).
- web/src/lib/feedback-client.ts — drop the unused PRODUCT_ID import.
- web/src/lib/notes-client.ts — delete the dead toWorkspaceSummary()
  helper. Workspace summaries are produced by listWorkspaceSummaries()
  on the backend response now; the local helper had no callers.

Web lint goes from 23 → 20 warnings. Remaining 20 are React-compiler
advisories about setState-in-effect patterns; those require careful
per-component refactoring (useReducer, derive-from-props, or
startTransition) and are tracked under Sprint D / Q1 tech debt rather
than fixed mechanically.
2026-05-22 23:51:59 -07:00
saravanakumardb1
aba7152097 fix(scripts): make ui-drift-audit work without ripgrep
The audit script silently passed on hosts without ripgrep installed
because 'rg -n ...' would fail, '|| true' swallowed the failure,
'matches' would be empty, and report() would print 'ok: no matches'.
This hid genuine UI drift from local 'pnpm run audit:ui' runs.

Changes:
- Detect ripgrep availability at startup and emit a stderr note when
  falling back.
- Add a grep-based fallback that translates rg '--glob !path' exclusions
  into 'grep --exclude=<basename>' so caller-side exclusions (e.g. the
  @bytelyst/ui adapter file at Primitives.tsx) still apply.
- Guard the optional 'extra_excludes' array expansion against 'set -u'
  when no exclusions are configured.

Result: on this host (no rg) the audit now correctly reports
2 categories with matches — raw interactive controls and legacy global
surface classes — instead of the false 'all green' it produced before.
'pnpm run audit:ui:strict' exits non-zero when matches remain, ready to
wire into CI once UI5–UI8 finish migrating the remaining call sites.
2026-05-22 23:51:47 -07:00
saravanakumardb1
9c65899387 feat(web/ui5): migrate auth pages and CreateWorkspaceModal to @bytelyst/ui primitives
Sprint C / UI5 — migrate the highest-leverage user-facing forms off the
legacy 'input-shell' / inline-style pattern onto the @bytelyst/ui Input,
Textarea, and AlertBanner primitives via the local Primitives.tsx adapter.

Adapter additions (web/src/components/ui/Primitives.tsx):
- Re-export AlertBanner, FormSection, and FieldGrid from @bytelyst/ui so
  product code never imports from the underlying package directly.

Migrated screens:
- web/src/app/(auth)/login/page.tsx
- web/src/app/(auth)/register/page.tsx
- web/src/app/(auth)/forgot-password/page.tsx
- web/src/components/CreateWorkspaceModal.tsx

Each migration replaces the ad-hoc 'input-shell' inputs and manual
label/error/success divs with the Input (label + hint props), Textarea,
and AlertBanner (tone='error'|'success') primitives. Inline style blocks
are replaced with Tailwind utility classes that read from the existing
--nl-* CSS custom properties so the visual tokens remain unchanged.

The 3 auth pages alone remove 9 input-shell call sites; the
CreateWorkspaceModal removes 2 more.

Verified:
- pnpm --filter @notelett/web run typecheck: passes
- pnpm --filter @notelett/web run test: 96/96 pass
- pnpm run verify: end-to-end green (backend 380/380, web 96/96, mobile 97/97)
2026-05-22 23:51:34 -07:00
saravanakumardb1
4667f85e20 docs(cleanup): move historical roadmaps to docs/archive/ and update AGENTS.md
Sprint B — closes audit item B7 (doc consolidation).

- docs/AGENT_TASK_ROADMAP.md, docs/ARCHITECTURE_REVIEW_AND_REUSE_ROADMAP.md,
  docs/GAP_ANALYSIS.md were each self-marked as historical snapshots
  but kept polluting the top of docs/. Moved them under docs/archive/
  in the previous commit; this commit:
  - Adds docs/archive/README.md explaining what's archived vs active
  - Repoints cross-doc links in docs/IMPLEMENTATION_TRACKER.md,
    docs/WEB_AI_FAST_ROADMAP.md, and docs/roadmaps/*.md to the new
    archive paths
  - Fixes relative links inside the archived files themselves so
    historical readers can still navigate back to active docs
- AGENTS.md §1.1 refreshed: reflects the May 22 re-verified state
  (382/96/97 tests), links the two new runbooks, and points readers
  away from docs/archive/ as a work source.
2026-05-22 23:23:50 -07:00
saravanakumardb1
bcad7d330a docs(runbooks): add MEK rotation and secret-management runbooks
Sprint B — closes audit items B4 and B5.

- docs/runbooks/MEK_ROTATION.md: step-by-step procedure for rotating
  the field-encrypt master key in Azure Key Vault, including pre-flight
  checks, rewrapAllDeks usage, verification queries, rollback, and lost-MEK
  recovery. Replaces the previous gap where MEK rotation had no
  documented operator path.
- docs/runbooks/SECRET_MANAGEMENT.md: inventory of every secret consumed
  by NoteLett with its production source (AKV), two production-grade
  patterns (workload identity vs K8s CSI), the compose-host pattern,
  rotation flow per secret type, verification commands, and red-flag
  triage.

Both docs cross-link each other and call out concrete open items
(automation, dual-JWT support, audit-log emission) for later sprints
rather than overstating current capabilities.
2026-05-22 23:23:38 -07:00
saravanakumardb1
1258d49488 feat(backend): emit task.created + workspace.created events; add share revocation regression test
Sprint B — closes audit items B6 (event-bus completeness) and B3
(public-share revocation regression).

Event bus:
- note-tasks/repository.ts createNoteTask now emits task.created with
  taskId, noteId, workspaceId, userId, title
- workspaces/repository.ts createWorkspace now emits workspace.created
  with workspaceId, userId, name

The event-bus already declared these event types (event-bus.ts) and
webhook subscribers can target them, but they were never emitted —
making the contract dead. Emissions follow the same .catch(() => {})
pattern used by note.created/updated/deleted in notes/repository.ts so
a subscriber failure cannot break the create flow.

Regression tests:
- note-tasks/repository.test.ts and workspaces/repository.test.ts
  exercise the emission paths end-to-end through the in-memory
  datastore.
- note-shares/repository.integration.test.ts adds a 5-test integration
  suite for the public-share revocation path: token resolves before
  revocation; token returns null after deleteShare (hard delete);
  expired token returns null; cross-product token rejected;
  listSharesForNote does not include revoked shares.

Verified:
- pnpm --filter @notelett/backend run test: 380/380 (was 373, +7 new)
- pnpm run verify end-to-end green
2026-05-22 23:23:08 -07:00
saravanakumardb1
c75ed3dc25 docs(sprint-a): record build restoration and refreshed sprint plan
- Commit previously untracked docs/NEXT_SPRINT_ROADMAP.md with refreshed
  May 22 status; mark Sprint 1 (backend build) and Sprint 2 (lint) as
  resolved by Sprint A workspace-path fix
- Add post-Sprint-A re-verification section to
  docs/PRODUCTION_READINESS_HANDOFF_ROADMAP.md documenting the
  workspace-path regression and the re-verified gates
- Update README quick-start to reference the canonical common-platform
  checkout path with BYTELYST_COMMON_PLAT_ROOT override note
2026-05-22 15:08:42 -07:00
saravanakumardb1
b2d824c8c6 fix(workspace): canonicalize common-plat path to ../learning_ai_common_plat
Restores green build after the May 12 Docker/UI regression.

Root cause: pnpm-workspace.yaml referenced a sibling path
(../learning_ai/learning_ai_common_plat/...) that did not exist on
dev/CI hosts. .pnpmfile.cjs fell back to ../learning_ai_common_plat for
some packages but missed others, so @bytelyst/ui was pulled from a
stale Gitea 0.1.0 tarball with zero exports (breaking web typecheck +
26 tests) and @bytelyst/monitoring was never linked into node_modules
(breaking backend typecheck + 2 test suites).

Changes:
- pnpm-workspace.yaml now references ../learning_ai_common_plat/packages/* directly
- .pnpmfile.cjs swaps DEFAULT/LEGACY common-plat roots so the canonical
  path is the default and the older nested path is the fallback
- scripts/docker-prep.sh, scripts/local-smoke.sh, scripts/release-guard-audit.sh
  follow the same canonical-first / legacy-fallback pattern
- .github/workflows/ci.yml symlinks directly to ../learning_ai_common_plat
- pnpm-lock.yaml regenerated with @bytelyst/ui@0.1.9 and
  @bytelyst/monitoring@0.1.5 linked to the local common-plat checkout

Verified:
- pnpm run verify: backend 373/373, web 96/96, mobile 97/97
- pnpm run audit:release-guards: passes
- backend, web, mobile lint all exit 0 (advisory warnings retained)
2026-05-22 15:08:30 -07:00
root
3dd981198e fix: Update docker configuration for production deployment
- Fixed NEXT_PUBLIC_NOTES_API_URL to use public API endpoint
- Updated docker-compose.yml environment format to proper YAML
- Updated Dockerfiles to remove Gitea secrets and use .docker-deps
- Added docker-prep.sh script for dependency packaging
- Changed NODE_ENV back to development for compatibility with memory DB

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-12 08:20:12 +00:00
root
4337793034 fix(docker): use shared docker-prep.sh and update Dockerfile for tarball approach
Some checks failed
CI — NoteLett / Backend — typecheck + test + build (push) Failing after 0s
CI — NoteLett / Web — typecheck + test + build (push) Failing after 0s
CI — NoteLett / Mobile — typecheck (push) Failing after 0s
CI — NoteLett / E2E — Playwright (push) Failing after 0s
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-10 01:50:55 +00:00
root
3c5856b2f5 perf(docker): optimize docker-prep.sh with caching and shared cache
Some checks are pending
CI — NoteLett / Backend — typecheck + test + build (push) Waiting to run
CI — NoteLett / Web — typecheck + test + build (push) Waiting to run
CI — NoteLett / Mobile — typecheck (push) Waiting to run
CI — NoteLett / E2E — Playwright (push) Waiting to run
Implemented 7 optimizations to significantly improve docker-prep.sh performance:
1. Git-based incremental builds (only rebuild changed packages)
2. Hash-based caching (content-addressable cache)
3. Persistent tarball cache (survives git clean)
4. Smart manifest tracking (track what's been built)
5. Cache-first build strategy (check cache before building)
6. Shared global cache (all products use same cache at ~/.cache/bytelyst-packages)
7. Custom cache location via BYTELYST_CACHE_DIR env var

Performance improvements:
- First build: 2-3 minutes (same as before)
- Subsequent builds: 5-10 seconds (cache hit)
- Multi-product deployment: 60% faster (6-9 min → 2.5-3.5 min)
- Disk usage: Reduced from 5.1MB to 1.7MB (shared cache)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-10 00:10:28 +00:00
root
fa00722a39 revert(docker): revert to docker-prep.sh approach due to workspace complexity
The base image approach is too complex for the current pnpm workspace structure.
Products cannot easily use the base image's workspace because pnpm expects all
workspace packages to be present during install. Reverting to the proven
docker-prep.sh tarball approach for now.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-09 23:35:37 +00:00
root
28189ac916 fix(docker): install all dependencies in builder stage for build tools
The base image only includes production dependencies, so we need to install
all dependencies (including devDependencies) in the builder stage to have
TypeScript and Next.js available for building.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-09 23:23:36 +00:00
root
b50e340f5a refactor(docker): use shared base images for @bytelyst/* packages
Update Dockerfiles to use bytelyst-common-base-backend and bytelyst-common-base-web
images instead of installing @bytelyst/* packages via tarballs.

Benefits:
- Smaller final images (~50MB vs ~250MB)
- Faster builds (base image cached)
- Consistent package versions across products
- No need for docker-prep.sh tarball packing

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-09 23:22:57 +00:00
root
3fe4f0786c feat(ux): add UX testing setup guide and common platform integration 2026-05-09 22:09:43 +00:00
a983e044b1 docs(ui): record shell css cleanup 2026-05-06 13:40:45 -07:00
db9b4557d8 refactor(ui): remove legacy shell css 2026-05-06 13:40:24 -07:00
2ba846698f docs(ui): record navigation boundary 2026-05-06 13:38:10 -07:00
7063e59078 refactor(ui): isolate notelett navigation 2026-05-06 13:37:49 -07:00
2a2c773ca1 docs(ui): record app shell migration 2026-05-06 13:36:25 -07:00
63211c0019 feat(ui): consume common app shell 2026-05-06 13:35:54 -07:00
4cfe5aee5e docs(ui): record review verification 2026-05-06 13:29:33 -07:00
6c562f05d8 test(ui): add review visual smoke 2026-05-06 13:28:57 -07:00
ca0f64e416 docs(ui): record review empty states 2026-05-06 13:19:23 -07:00
6472a58ad1 feat(ui): add review empty states 2026-05-06 13:18:52 -07:00
192a2aafde docs(ui): record review keyboard shortcuts 2026-05-06 13:16:01 -07:00
d63fdd1def feat(ui): add review keyboard shortcuts 2026-05-06 13:15:37 -07:00
116c0c982b docs(ui): record review component split 2026-05-06 13:14:18 -07:00
de75d93e59 refactor(ui): split review workflow components 2026-05-06 13:13:46 -07:00
936d2899fe docs(ui): record review primitive migration 2026-05-06 11:49:06 -07:00
c79aa2b6fd feat(ui): migrate review workflow primitives 2026-05-06 11:43:34 -07:00
1784f72d70 docs(ui): record adapter defaults 2026-05-06 11:36:42 -07:00
cf5e9c03fd feat(ui): add notelett primitive defaults 2026-05-06 11:36:05 -07:00
02c691bd8e docs(ui): record token alias migration 2026-05-06 11:34:46 -07:00
0f5ddb400b feat(ui): map platform tokens globally 2026-05-06 11:34:23 -07:00