Skip to content

feat(app-factory): P1 backend data layer + @nuncio/contracts - #1

Open
oscarlehuu wants to merge 11 commits into
mainfrom
app/app-factory
Open

feat(app-factory): P1 backend data layer + @nuncio/contracts#1
oscarlehuu wants to merge 11 commits into
mainfrom
app/app-factory

Conversation

@oscarlehuu

Copy link
Copy Markdown
Contributor

Summary

App Factory P1 backend (TDD-first): mirror the screensdesign catalog into local SQLite, keep it fresh, and expose 12 typed WS RPCs — foundation for the App Factory sidebar (web UI in P2).

  • @nuncio/contracts (first @nuncio/* package): App Factory domain schemas, RPC inputs/results, typed errors. @synara/contracts re-exports it — existing imports unchanged.
  • Migration 88+89: af_apps, af_app_revenue, af_videos, af_screens, af_watchlist, af_sync_runs, af_state; detail_fetched_at for lazy-detail tracking.
  • AppFactoryRepository: catalog/revenue/media/watchlist persistence, sync-run lifecycle, state KV. Catalog upserts COALESCE detail-only columns so syncs never erase refreshApp data.
  • ScreensdesignClient: bearer auth via ServerSecretStore, typed errors (401/403/404/429), Retry-After backoff, lenient wire schemas (string-number coercion).
  • CatalogSync: full/incremental sync, cursor resume, single-flight, crash recovery; 401 mid-sync ends run as token_invalid with local data intact.
  • AppFactoryService + wsRpc: token lifecycle (requireOwner for set/clear), status with live progress, list/detail, pin/note. Detail view lazily mirrors detail+videos+screens on first view, then cache-forever; without a token it serves local cache.

Test plan

  • 195 scoped tests green (contracts, repository edge cases, client error paths from real API fixtures, sync modes incl. resume/single-flight, service facade)
  • Migration tracker + lineage-replay fixtures (19 tests)
  • bun fmt, bun lint clean; bun typecheck at baseline (2 pre-existing PiAdapter errors)
  • CI green
  • Dogfood: set token in dev instance, run full sync, browse list/detail

Review notes

Bugbot review run on this branch; 6 findings addressed in 002da083 (2 high: detail-field wipe on sync, stale row in detail response; 4 medium: no-video lazy gap, empty-catalog guard docs, owner gate, token-less detail view).

Made with Cursor

oscarlehuu and others added 11 commits July 28, 2026 15:53
Repo-wide bun fmt run reformatted these pre-existing files (markdown
tables, HTML). No content changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
App Factory research-tool backend, built TDD-first:

- @nuncio/contracts (new package): App Factory domain schemas, RPC
  inputs/results and typed errors. @synara/contracts re-exports it, so
  existing imports are unchanged; WS_METHODS, request-body union and RPC
  group registrations stay in Synara ground as the shared socket registry.
- Migration 088: af_apps, af_app_revenue, af_videos, af_screens,
  af_watchlist, af_sync_runs, af_state + indexes.
- AppFactoryRepository: catalog/revenue/media/watchlist persistence,
  sync-run lifecycle, state KV. json_each for large NOT-IN sets.
- ScreensdesignClient: bearer auth via ServerSecretStore
  (screensdesign.api-token), typed error taxonomy (401/403/404/429),
  Retry-After-aware rate-limit backoff, lenient wire schemas with
  string-number coercion.
- CatalogSync: full/incremental sync with cursor resume, single-flight,
  crash recovery; resumed full syncs skip removed-marking; 401 mid-sync
  ends the run as token_invalid with local data intact; refreshApp
  re-mirrors one app (404 -> removed flag, revenue preserved).
- AppFactoryService facade + 12 WS RPCs: token lifecycle (account email/
  isPro cached in af_state), status with live sync progress, list/detail,
  pin/note. Videos/screens mirrored lazily on first detail view, then
  cache-forever in SQLite.

Tests: 53 scoped (contracts, repository edge cases, client error paths
from real API fixtures, sync modes incl. resume/single-flight, service
facade). Full server suite 2996 green. Migration tracker expectations
updated for entry 88.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bugbot review findings, each with regression coverage:

- Catalog syncs wiped detail columns: list payloads lack description/
  appstoreLink/storeId/categoryPrimary/latestAppobvideoId, and the upsert
  overwrote them with null on every run. Syncs now use upsertCatalogApp,
  which COALESCEs detail-only columns on conflict; refreshApp keeps the
  full-overwrite upsert.
- getAppDetail built the response from a stale in-memory row, so the
  first detail view still showed null description/link after the lazy
  refresh wrote them. The row is re-read after refresh.
- Lazy refresh keyed off "no videos" skipped apps without recordings
  forever. Migration 89 adds af_apps.detail_fetched_at (guarded ALTER,
  replay-safe); refreshApp stamps it, getAppDetail triggers on it.
- Token RPCs were unauthenticated-mutation: setToken/clearToken now
  requireOwner like other secret-management RPCs.
- getAppDetail without a token no longer errors on upstream calls: it
  serves the local cache (screens stay null per contract), zero fetches.
- Documented the empty-catalog removal guard: an empty seen-set is a
  no-op so an anomalous empty upstream page can't mass-mark removals.

Tracker expectations updated for migration 89. Suites: repository 14,
CatalogSync 9, service 10, migrations 19, scoped total 195 green.

Co-authored-by: Cursor <cursoragent@cursor.com>
The turn-resilience checkpoints landed the compaction_start handler and
the extended PiSessionContext interface but lost two pieces, breaking
typecheck on main (CI red since):

- piCompactionTitle helper (reason-aware compaction titles), restored
  verbatim from checkpoint 2dab4881.
- PiSessionContext construction was missing activeTurn,
  pendingTurnOutcome, turnWatchdog, turnActivityAt.

Co-authored-by: Cursor <cursoragent@cursor.com>
Main moved to @nuncio/* identity and upstream v0.6.3 while App Factory P1
was in flight. This merge:

- Folds App Factory schemas into the rebranded @nuncio/contracts
  (packages/contracts/src/appFactory.ts); deletes packages/nuncio-contracts.
- Renumbers App Factory migrations to 089/090 (088 taken by
  ExternalMcpAudienceIdentity); 090 made idempotent for lineage replays.
- Renames remaining @synara/contracts imports and synara/ service tags.
- Fixes pre-existing main breakage that kept CI red: PiAdapter typecheck
  (piCompactionTitle + PiSessionContext fields), release-smoke bin
  expectation (ade alias), 035 test import, ACP log redactor entry names
  (both identities), TOML escape fixtures, 077 fresh-install audience,
  web store tests (vitest setup installs in-memory localStorage over
  Node's unbacked global).

Gate: fmt/lint/typecheck/brand:check clean; full tests 8/8 workspaces.
Co-authored-by: Cursor <cursoragent@cursor.com>
Node unit tests need an in-memory localStorage before zustand persist
resolves storage at import time. Browser mode already has real
localStorage — clear setupFiles inheritance in the browser config and
only install the stub when globalThis.localStorage is undefined, so
Chromium runs are not redefined by the node stub.

Co-authored-by: Cursor <cursoragent@cursor.com>
Importing defineConfig from vitest/config pulled the vitest graph into
the shared Vite config that browser mode merges. CI then hung for the
full 20m timeout at "[optimizer] bundling dependencies...". Restore the
vite import and type the test.setupFiles field via a vitest/config
triple-slash reference instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fail-fast local gate matching the Ubuntu quality job order
(brand → fmt → lint → typecheck → migrations → smoke → tests →
optional browser/build). Use ci:local:fast while iterating,
ci:local before push, ci:local:web when touching web/vite/vitest.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add docs/CI-LOCAL.md (tiers, GitHub step mapping, hang signals, cheat-sheet),
point Living Docs + Dev Workflow + Task Completion at it, note in STATE and
DECISIONS so future agents discover the pre-push gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
Browser tests merge vite.config and were hanging 20m at dependency
optimize whenever node setupFiles lived there. Move the localStorage
stub into apps/web/vitest.config.ts (unit tests only); vite.config.ts
matches main again. Includes pending ci:local script + agent docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
brand: check flags non-exempt docs; rephrase as upstream-inherited quality gate.
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant