Цель: новая сессия должна за 5 минут понять где остановились и продолжить. Читай в порядке: этот файл →
Rules.md→Implementation_Workflow.md→ конкретная Phase.
⚠️ Repo переехал в org:
Arnomake/arno(былvadimpianov/arno; старые URL ниже редиректят, не битые). Remote =github.com/arnomake/arno.ghкоманды с--repo vadimpianov/arnoвсё ещё работают через redirect, но canonical —Arnomake/arno.
⚠️ CROSS-SESSION (2026-09-07):
feat/sorg-layoutis now landed into through a MERGE QUEUE — direct push is rejected (rulesetshared-trunk-merge-queue, ADR 0089). Several sessions share this trunk because they share one review surface. Measured before the change: 17 direct pushes in a day, deploys serialised behind a per-branch FIFO (7-11 min, 15-17.6 min when overlapping), and four consecutive red runs froze the stand for ~30 minutes for everyone. New flow: work onland/<topic>→ PR intofeat/sorg-layout→gh pr merge --auto --squash. Nothing else; the queue orders, batches (up to 5), runsstand-gateon the speculative commit, and ejects a failing PR while merging the rest. A PR touchingtools/migrate/drizzle/**rides alone —stand-gatefails a mixed batch on purpose, because an applied migration on the shared test Neon cannot be un-batched the way code can.deploy-test.ymlis nowgate→freshness→deploy: the gate is per-SHA and never cancelled (and skips itself whenstand-gatealready passed that SHA), a superseded commit is not deployed, and the deploy is cancellable — the stand shows the newest commit instead of replaying intermediate ones. Every otherfeat/**/fix/**branch keeps its owntest-<topic>stand and direct pushes.
⚠️ PROD-MIGRATION GOTCHA (Punta spacing SSOT, commit
4987fb3). Container spacing moved from CSS classes into the tree (node.gap); the.slc__*group-layout classes no longer carrygap, and.slc__grouplost itsmargin. Existing saved Sorgente compositions (rows insorgente.composition_instance) predatenode.gap, so on load they render with collapsed gaps. The test project was re-seeded by DELETEing its 5 sub-tab compositions (Studio reverts to the newdefaultTree). When this ships to prod, prod's saved Sorgente trees must be re-seeded the same way (delete the composition rows for/sorgente/tokens/{colors,typography,spacing,radii,effects}viaDELETE /api/v1/sorgente/composition?routePath=…, maintainer-auth), or the catalog spacing collapses. No schema-guard auto-migration was added.
⚠️ CROSS-SESSION (2026-07-09,
feat/sorg-layout): single-leaf MERGE is DISABLED. User rule: "all folders behave the same — a container with ≥1 child is a folder; delete its last child and it's pruned".mergedLeafChildnow always returns null (layers/single-child.ts), so a box with one leaf child is a normal expandable folder, NOT a merged one-row leaf. This supersedes the mergedLeafChild-based leaf-detection a parallel session built: Dito (action-registry.tsisLeafLike,Dito.tsx:170) and Punta (BoxSidebar.tsxhideTracks) now treat any single-leaf box as a FOLDER — full Dito toolbar + TRACKS shown. Specimens are exempt because they are no longer box-wrapped: a load-time migrationunwrapSpecimenBoxes(types.ts, in the useStudioTree chain) promotes any container whose only child is a specimen (ColorSpecimen/Typography/Primitive/Spacing/Radius/Shadow) to a bare leaf — the ~34 drifted swatch cards self-heal to clean leaf rows. So: specimen = leaf; every other container = folder. Do NOT re-enable merge to "fix" swatches. Commits286407d(Cmd+G group + Shift-range-select),2690813(merge disable),4407579(specimen unwrap).
⚠️ CROSS-SESSION (2026-07-09,
drop-tracker.ts): drag-COMBINE added +draggedParentIdhelper completed. New feature: dragging a row onto another and pushing right combines them into a new folder (combineLeafIdon the drop target → shell calls tree-opscombineIntoFolder). While wiring it, a parallel session's UNCOMMITTED edit tomergeIntoreferenceddraggedParentId(tree, activeId)with no definition — the whole app failed typecheck. I added the obvious 1-line helper (findNode(tree,id)?.parent?.id ?? null) to unblock; if that session also defines it, drop the dup. TheirReel.tsx/PuntaIcons.tsxWIP was left untouched (not committed by me).
Session 2026-08-20 — Instant sidebar tab switch: Workflow ⇄ Design system keep-alive (branch feat/sorg-layout)
Sidebar rail switch Design system ⇄ Workflow was ~1–3.5 s. Diagnosed live
(browser-driven, JS-instrumented) and fixed in three layers, all on
feat/sorg-layout, deployed to test-sorg-layout.arnomake.com:
eff38782— per-project cache inuse-workflow-artboards.ts/use-workflow-artboard-links.ts: state survives the route unmount, no refetch on re-entry (killedworkflow-artboards+entity-links, ~690 ms each).5977496e—experimental.staleTimesinnext.config.mjs: Next 14.2 client Router Cache keeps the RSC payload, so the tab switch stops re-fetching the destination RSC (~0.5 s warm, up to 5 s cold through the preview proxy).84d2496b+15df644c+c98f3aaf— newapps/web/src/features/studio-workspace/(StudioWorkspace): keep-alive host — both surfaces mount once, a tab switch flips panevisibility(notdisplay, so the canvas fit camera stays valid) +history.pushState(URL mirrors the tab,?projectpreserved), no route navigation, no remount.popstatesyncs back/forward. The two heavy surface elements areuseMemo'd so the flip does NOT re-reconcile them (that ~1 s reconciliation was the last floor). Idle surface pre-warmed onrequestIdleCallback. Rail: Workflow/Design system are in-place<button>s (useWorkspaceTab); editor/docs/backend stay<Link>routes.
Both /app/workflow and /app/library boot StudioWorkspace with their own
initialTab — deep-links + refresh land on the right tab. Full doc comment in
StudioWorkspace.tsx is the source of truth.
Measured live (switch = MutationObserver on pane data-active):
- Seed (~800 boxes): 20–40 ms both directions (was 1.7–3.5 s / ~1.0 s).
- Stress test — 100 real artboards (11.5k boxes, 24 985 DOM nodes, tokens + DS components; seeded via API PUT then deleted, seed restored): switch 60–84 ms — still under 100 ms.
24/7 concern (huge files open all year) — verified safe:
- Parked surface is fully inert: 817-box AND 11 497-box parked Workflow →
0 DOM mutations over 6–8 s idle, heap Δ ~1–2 KB. No continuous rAF loop /
setInterval/ infinite CSS animation anywhere in the canvas stack (tela/compasso/quadro/albero/punta/layers/artboards-canvas) — canvas is event-driven, idle = ~0 CPU regardless of file size. Hidden pane also haspointer-events: none. - Memory: both surfaces resident (seed ~33 MB; 100 artboards ~134 MB), no leak (parked tree static). Scales with file, one-time not continuous.
- Initial load of a 100-artboard file ~7.7 s — inherent big-file render cost, not keep-alive (old route-remount would pay it on every switch instead of once).
Cross-talk bug + fix (e6a24ae4). Keep-alive broke the studio-bus invariant
"one mounted surface = one publisher": the parked surface still called
registerActiveTree, so the visible surface's Layers/Punta rendered the parked
tree (after refresh + idle pre-warm the Workflow tree bled into the Design system
/ Component Builder view; the shared selection outline dropped). Fix: module store
features/studio-workspace/active-surface.ts (setActiveWorkspaceSurface +
useIsWorkspaceSurfaceActive, via useSyncExternalStore so the gate doesn't
re-render the memoised tree). Both tree publishers now gate on their surface being
the visible tab: workflow-composition-artboard.tsx (!active || !surfaceActive)
and the Library ComponentBuilder visible (&& libraryActive). Mirrors the
pre-existing component-builder/PageArtboard.tsx visible gate. Verified live
(both triggers: manual switch, and refresh+pre-warm). Rule: any new keep-alive
surface / registerActiveTree publisher MUST gate on useIsWorkspaceSurfaceActive.
See user-memory reference_studio_bus_single_active_surface.
Open follow-ups: if 100-artboard files become the norm and load/memory bite,
the real lever is artboard virtualization/culling (render only viewport
artboards) — helps initial render + memory + switch together. Not built.
Verified: pnpm --filter @arno/web typecheck, next build (29/29 static),
workspace-tabs L1 test, workflow/page.invariants, live L8 smoke.
Session 2026-07-16 — Workflow/Screen edits stopped resetting; Sorgente gets a real project row (branch feat/sorg-layout)
Reported as "switching sidebar tabs resets my edits" on
test-sorg-layout.arnomake.com/app/workflow/?project=prj-arno-sorgente-seed.
Three stacked causes, all fixed + verified live via the maintainer's own
Chrome session (not the sandboxed preview — auth required, no credentials
entered).
Root cause 1 — two useWorkflowArtboards instances. Workflow
(ArtboardsCanvas) and Screen (ProjectScreenCanvas) each called the hook
independently; switching tabs unmounted one, cancelling its in-flight
600ms-debounced saveTree before the other's fetch landed. Fixed by lifting
the hook to WorkflowShell, passed down as props to both (contract
W7, S7).
Root cause 2 — Punta Typography never rendered. size/weight/line/
tracking staged correctly into node.props (Punta write path fine) but
TextFactory (registry.tsx) never read them back — edits were invisible
and, for heading/subheading variants, not even attempted past family. Fixed
in render/text-style.ts (textTypographyOverride, unit-tested).
Root cause 3 (the actual "still resets" repro) — prj-arno-sorgente-seed
had no project-table row, so every GET/PUT of its workflow_artboard
404'd ("Project not found") — nothing ever reached the DB regardless of
fix 1. A parallel session hit the same 404 mid-session and shipped the
opposite fix (c32c959/5fc906d: permanently skip persistence for
Sorgente) — reverted, since it directly blocks the stated need (Sorgente
used as a real project by Arno's own interface developers, not just a token
showroom). ADR 0071
gives it a real row instead (one-shot seed-sorgente-project-row.yml, run
- removed same session, PRs #96/#97 on main). Verified: edit → Workflow ↔
Screen → full reload survives;
/api/v1/me/projects+ the projects-list pinned-card dedup (ADR 0054 §5) confirmed no duplicate card.
Gotcha for future sessions: if workflow-artboards 404s again for
Sorgente, the row got dropped (e.g. a test-DB rotation restored from a dump
older than 2026-07-16) — re-run the pattern in ADR 0071 §Decision, don't
reach for the skip-persistence special case again.
Session 2026-07-15 — Tela: Adaptive removed + per-project default; Workflow artboard editing (branch feat/sorg-layout)
All shipped to the test-sorg-layout stand and browser-verified. Commits
c86a91f…a14373c (interleaved with other sessions on the shared branch).
Tela — Adaptive removed → configurable, persisted Default device:
- Adaptive (the
size===nullno-frame mode) is gone from the strip; a device frame is always active.DEFAULT_TELA_SIZE = 1440×900seeds the active + a newdefaultSizestate (getDefaultSize/setDefaultSize/subscribeDefaultSize/isDefaultSize). The Default device (radio in Tela settings) is the base-editing context for per-width overrides — replaces Adaptive's role (ADR 0065 amendment). node-bridge (gap) + StudioRenderer (columns) gate onisDefaultSize, notsize===null. - The default is a persisted per-project setting — new column
project.default_viewport jsonb(migration20260714140000, applied on live test-Neon viamigrate push; DB-default backfills existing rows). API: create assigns 1440×900,/me/projectsreturns it,PATCH /projects/:idaccepts it (validatorapps/api/src/viewport.ts+ test). Client:ProjectMeta.defaultViewport,updateProjectViewport,useProjectViewport; the Workflow page seeds Tela on open + PATCHes the Default change back. ADR 0070.
Workflow tab — composition artboards are editable device frames:
- Artboards size to the active Tela preset; the ROOT fills the frame via a new
StudioRendererfillHeightprop (.studio--fill-height, rules in StudioRenderer.css, guarded byfill-height-css.test.ts) — so selecting the artboard edits its own fill/stroke/radius/padding in Punta (clicking the frame picks the root; ⌘-click picks a box). Frame chain transparent → no default border, radius rounds the artboard. Seed = Stack ("Row") primitive + concrete values (#ffffff,24px,8px). Selection matches the DS/Inspector Tweak look (gradient ring + hollow gradient corner diamonds), counter-scaled by1/zoom. Library rail + zoom pill temporarily hidden behind flags. - Docs:
docs/workflow/architecture.md §Workflow-tab artboard editing.
Gotchas hit: committing page.tsx by pathspec swept a parallel session's
uncommitted ProjectScreenCanvas WIP (untracked file → CI TS2307); reverted
the committed side to SorgenteLibraryCatalog, left their WIP unstaged
(0410fb6). The test-sorg-layout stand persists Workflow artboards in
localStorage (Sorgente is synthetic, no DB row) — reset by clearing
arno.workflow.* keys. Stand assets carry a contradictory Cache-Control
(no-store + immutable); server serves fresh, but browsers need ⌘⇧R.
Session 2026-07-14 — feat/sorg-layout → dev + DEV_DATABASE_URL retirement
Landed the Studio subsystem on dev. Merged feat/sorg-layout → main via
PR #92 (opens in a new tab) (288 commits: full Sorgente
Studio + @arno/{ponte,tela,punta,layers,dito}, ADRs 0055/0063/0064/0066).
Gate: full pnpm --filter @arno/web exec tsc --noEmit clean → PR opened →
main-CI green (verify 5m15s; only the advisory suggest bot flaked). ci.yml
auto-deployed backend arno-api-dev + FE dev.arnomake.com. Prod untouched
(main→prod is manual since 2026-07-03). Merge also pulled the proxy pin-fix #91
into the branch (clean, no conflicts).
Verified on dev (drove the maintainer's authed Chrome). dev.arnomake.com/app/library
renders the seed composition identical to test-sorg-layout; all API calls hit
arno-api-dev with GET /api/v1/sorgente/state → 200. No data copy was
needed: dev + test both resolve the Sorgente DB to the rotating arno-test-active
by Neon project name (ci.yml reseeds the dev worker's DATABASE_URL +
SORGENTE_DATABASE_URL each push) — the ADR 0052 "separate dev Neon" is dead.
Bundle parity confirmed (core Studio chunks byte-identical dev vs test).
Retired DEV_DATABASE_URL (PR #93 (opens in a new tab)).
It was read by nothing live (ci/release/deploy-test resolve by project name), only
by 6 vestigial manual one-shots against a now-deleted separate dev Neon: deleted
apply-sorgente-{schema-all,component-entry,layout-override}, debug-dev-sorgente,
debug-db-secrets, seed-sorgente-secret-test. Corrected the stale HANDOFF
warning that claimed the prod release path was broken (release.yml was already
fixed to resolve arno-test-active by name). Then deleted the orphaned GH secret
DEV_DATABASE_URL itself. User-memory reference_arno_release_sorgente_step_broken
updated to "resolved".
Still open (unchanged by this session): feat/sorg-layout branch stays (Studio
work continues on it, worktree active). Temp GET /api/v1/sorgente/_debug/conn
still in sorgente.ts. 6-secret rotation. Eventual full prod release ships the
Studio work with it.
Session 2026-07-12 — Full-repo review + remediation (batches 1-5) + security hotfix to prod
Full-repo code review (6 dimensions via subagents: frontend/backend quality,
tests, security, performance, infra) → triaged map → fixed and shipped batches
1-5. Every finding was re-verified against origin/main before acting — the
agents were pointed at the divergent local ~/Desktop/Claude/Arno main, which
carries a superseded architecture (old per-env arno-sorgente.ts model, a
different workflow set, red port-v9-synth-tokens typecheck, a 2s sorgente-overlay
poll), so ~40% of the raw findings were local-main phantoms that do not exist
on canonical (e.g. a destructive wipe-non-allowed-users.yml, a migration-journal
desync). Lesson recorded in user-memory project_arno_local_main_divergent: audit
Arno from an origin/main worktree, not local main.
Merged to main (3 PRs, all CI green):
- #87 (opens in a new tab)
fix(security)— P0 OAuth open redirect → JWT theft → account takeover:auth.tsused the caller'sreturn_toverbatim as the JWT-bearing redirect with no allowlist. Addedcors.safeReturnTo()(reuses the origin allowlist via newisAllowedOrigin()), validated at login + callback. P1 stored XSS: captured SVG rendered viadangerouslySetInnerHTMLin the arno origin → now rendered through an<img>data URI (browser never executes script in image-loaded SVG). +6cors.test.tscases. - #88 (opens in a new tab)
ci—ci.ymlnever ran@arno/foundation(104) +@arno/shared(17) tests on the main gate; added the steps (121 tests). - #89 (opens in a new tab)
fix(db)— hot-path indexes onproject.owner_id/staged_component(user_id,last_activity_at)/component_md_versions(project_id,file_path,saved_at)(were seq-scans; migration20260712120000_hot_path_indexes, additive); stop leaking raw neon() error text to clients (url-imports/captured-pages/fonts→ letapp.onErrorreturn opaque 500);url-importsPOST now gatesproject_idownership;CompositionRendererwrapped inReact.memo. +5 regression tests.
Security hotfix shipped to prod (isolated). Prod (arnomake.com) was last
released 2026-06-09 (83f9815); main is ~445 commits ahead (a month of
unreleased studio/sorgente/fonts work, some unverified). Per maintainer decision,
did not full-release main — instead a surgical hotfix: branch from the prod
SHA + cherry-pick only the security commit (applied cleanly — the vulnerable
lines were unchanged since June) → gh workflow run release.yml --ref hotfix/....
OAuth + SVG fixes are live on prod; the month of features is not. Hotfix branch
deleted after. #87/#88/#89 are on main + dev, will ship with the eventual full
release.
✅ Release path fixed (was: broken 28P01 on globals.css). release.yml step
"Regenerate globals.css from sorgente schema" no longer reads the stale
DEV_DATABASE_URL secret — it resolves the Sorgente DB by Neon project name
(arno-test-active) via NEON_API_KEY, DATABASE_URL_TEST fallback (same
resolver as ci.yml / deploy-test.yml). DEV_DATABASE_URL is now referenced by
nothing (2026-07-14): the vestigial DEV_DATABASE_URL-based one-shots
(apply-sorgente-*, debug-dev-sorgente, debug-db-secrets,
seed-sorgente-secret-test) were deleted and the GH secret retired. Sorgente
schema is live on arno-test-active (dev + test share it, resolved by name).
Deferred follow-ups (out of the batch-1-5 scope, noted for a future session):
- N+1 serial GitHub calls in
sync.ts(createBlobloop) andwebhooks.ts(getContentloop) — parallelize with bounded concurrency (GitHub secondary rate limits);deploy.tsalready shows thePromise.allpattern. - Coverage for the highest-consequence untested backend paths (
sync.tsmerge/ conflict,gdpr.tsdelete-scoping,api-client.ts401-retry) — needs an integration harness (real Postgres) / jsdom; mock-only tests would be tautological. - God-file splits (
composition-renderer.tsx1352,api-client.ts1298,index.ts525 business logic) + the feature-folder migration — the review's "batch 6", intentionally left as trigger-driven per the Rules migration policy. - Fix the broken release sorgente step (above) before the next full prod release.
Session 2026-07-08 — Token-optimization sweep (docs/memory/hooks, no app code)
Session-context cost reduction, no product code touched. Three doc PRs to
main: #82 (opens in a new tab) (archive sessions
≤2026-06-07 → HANDOFF-archive.md), #83 (opens in a new tab)
(Rules: read HANDOFF via offset), #84 (opens in a new tab)
(archive DONE-milestone logs + Rules: delegate code investigation to subagents,
Dito naming invariant relocated from user-memory). Net: HANDOFF.md 1683 → 975
lines (−42%); completed-milestone + old-session detail lives in
HANDOFF-archive.md (read only when old history / an evergreen section is
actually needed).
Non-repo changes (recorded here so they're not invisible): removed the
duplicate local Figma MCP server (figma-mcp @127.0.0.1, Dev-Mode-only) from
~/.claude.json; added Tools/Hooks/handoff_size_check.sh (SessionStart
reminder to re-archive when HANDOFF > 1400 lines, reminder-only — never mutates
the file, since worktree sessions edit it concurrently); consolidated global
user-memory 52 → 40 notes.
⚠️ Local checkout caveat. The ~/Desktop/Claude/Arno main is an intentionally
divergent branch — it carries a local commit adapting the /build multi-agent
TDD workflow (.claude/) to run at the Arno repo root (apps//packages/),
whereas origin/main holds the generic project/-subdir form. Do not ff /
merge / reset local main to origin (it would clobber the adaptation and break
/build-in-Arno). When origin doc files move ahead, sync surgically:
git checkout origin/main -- HANDOFF.md HANDOFF-archive.md Rules.md _index.md.
There is also unrelated uncommitted WIP (CVD/daltonism filter) in the working
tree — don't sweep it into commits. Full detail in user-memory
project_arno_local_main_divergent.
Session 2026-07-12 — Phase 17: Studio tools → reusable @arno/* packages (branch feat/sorg-layout)
Done + browser-verified on the stand. The Studio kernel and the four live
tools were extracted into workspace packages so an edit propagates to every
importer via workspace:* with no copies (ADR 0063, Implementation_Workflow.md
Phase 17):
@arno/ponte(packages/ponte/) — shared kernel:studio-bus,types,tree-ops,adaptive/primitives,render/even-size,single-child, telemetry (studio-events, transport injected viaconfigureStudioEvents, wired app-side instudio/configure-ponte.ts).@arno/{tela,punta,layers,dito}— the tools; imported by name (import { Punta } from "@arno/punta"), NOT fromstudio/<tool>/.useStudioTreestayed app-side (binds persistence).single-child(mergedLeafChild) was promoted fromlayers/into@arno/ponte— so the 2026-07-09 cross-session note above aboutmergedLeafChildnow lives atpackages/ponte/src/single-child.ts, imported as@arno/ponte/single-child(behavior unchanged: always returns null). Tool DAG is acyclicponte ← punta ← {layers, dito};telastandalone.- Kernel shims removed; app-side studio code (
render/,sections/,seeds/,inspector/tweak+ruler, canvas catalog) imports@arno/pontedirectly. - Chord-debt:
@arno/layersis a generic name — rename toordito/stratiin a later ADR (ADR 0059).
Commits (all on feat/sorg-layout): 2fb922d ponte · 71133be tela ·
c034108 punta+single-child · 3120f10 layers · 83ff95a dito · 06d3763
layers-scaffold repair + shim removal · bf33be1 browser-verified. Note:
3120f10/83ff95a are internally incomplete (a concurrent session sharing this
worktree disturbed the index between add and commit; layers scaffold landed in
06d3763) — harmless under squash-merge; HEAD is complete + green.
Verified: 595 package tests + @arno/web typecheck/tests + pre-commit
monorepo typecheck/lint. Browser regression on test-sorg-layout.arnomake.com
/app/library under auth: pick (layers→ponte-bus→punta), tela preset→device
frame, punta instant-write→canvas, dito hover panel — all work across package
boundaries; studio-bus singleton deduped; zero console errors.
Pending: proof-of-reuse (import a tool in a SECOND part of Arno — capability
ready, not yet demonstrated); the @arno/layers chord rename.
Session 2026-07-04 → 07-07 — Punta/Layers polish + ship to dev (branch feat/sorg-layout; PR #79 to main)
All on feat/sorg-layout. feat/sorg-layout was MERGED to main (PR #79) →
dev pair (dev.arnomake.com + arno-api-dev) now carries the Sorgente Library
surface. Later Layers/Punta commits are on the branch, NOT yet re-merged to
dev (maintainer hasn't re-approved a merge).
Shipped & verified live (test-sorg-layout.arnomake.com):
- Layers container glyphs = the node's assigned Punta primitive (shared
registry
adaptive/primitives/primitive-meta; Layers + Punta can't drift). - Box-gap
|·|even-snap on write (node-bridge.setGap) — kills odd-px on canvas + the migrate round-trip "jump". useStudioTreedirtyRef— a stale Sorgente poll no longer clobbers an actively-edited tree (the "gap jumps around the 3rd arrow click" bug).- Sorgente egress −99%:
/api/v1/sorgente/versionchange-token poll (4s, hidden-tab pause, debounced refresh) replaces the 2s full-state loop. - Collapse-all / expand-all toggle in the Layers header (pinned to the rail
edge, symmetric to the root icon;
expandableNodeIdsincollapse-all.ts). - Leaf-row cleanup: a leaf (and a single-leaf box, which IS a leaf to the
designer) shows ONLY its Component/Text icon — no primitive, no padding
glyph. Padding badge lives on REAL folders (multi-child) that own padding,
shown only when padding > 0 (
ownPaddingSides/hasOwnPadding). Punta TRACKS lists container children only (leaf children removed — can't take a primitive). - Logo short-form + pinned "Arno Sorgente" card resolve the seed name/gate statically (survive test-DB reset/rotation).
Infra fixes this window:
feat/sorg-layoutwas 4 commits behind main → the CI "merge origin/main" preview step conflicted and failed EVERY deploy. Fixed by merging origin/main into the branch (clean). Keep the branch current or deploys break for all sessions on it.- deploy-proxy:
dev.arnomake.comresolved via the Pages canonical deployment, but CI ships dev as a BRANCH preview → froze on an old build. Now resolves<branch>.<project-subdomain>alias (PR #80). - ci.yml dev pair rides the rotating test DB by Neon project name
(
arno-test-active) — the deadDEV_DATABASE_URLhad been failing every main push. Rotation reseeds the dev worker too. - ADR 0052 amended (PR #78): Sorgente storage = the rotating test DB, not "dev
Neon".
release.ymlSorgente export re-pointed to the resolved test DB (DEV_DATABASE_URLwas a deleted endpoint — next prod release would 've failed).
Neon one-time setup DONE: NEON_API_KEY repo secret set; test project
renamed arno-test-active. Nightly backup + hourly quota-lock auto-rotation
live on main (verified by a forced rotation: user id + JWT survived).
Pending: temp GET /api/v1/sorgente/_debug/conn still in sorgente.ts
(remove after a week clean). Sorgente historical rows on the Arno project's
production branch aren't read by any worker — import decision open.
Session 2026-07-03 — Test-DB survival infra + Sorgente rewiring (branch feat/sorg-layout + main PRs #75-77)
Free-tier Neon quota (HTTP 402) had been killing the test stand. The stand is now self-healing, and the session surfaced + fixed a wrong assumption about where Sorgente data lives.
Infra shipped (workflows live on main — cron/dispatch require default branch):
test-db-backup.yml— nightly 03:00 UTCpg_dumpof the active test DB →test-db-dumpartifact (30 days). The dump carries ALL schemas incl.sorgente.*.test-db-rotate.yml— hourly/readyprobe; on quota lock: create fresh Neon project → restore latest dump → top up migrations → rename old →arno-test-retired-<run>/ new →arno-test-active→ reseed BOTH worker secrets (DATABASE_URL+SORGENTE_DATABASE_URL). Snapshot-forward only, никаких merge. Проверено боевымforce=trueпрогоном: user id, JWT и Sorgente-данные переживают ротацию.deploy-test.yml— резолвит живую тест-БД ПО ИМЕНИ Neon-проектаarno-test-activeчерезNEON_API_KEY(fallback:DATABASE_URL_TESTsecret). Сеет оба секрета воркера из одного resolved URL.- Neon аккаунт — organization-scoped:
GET /projectsбезorg_idпуст; резолверы обходят personal + все org scopes. tools/migrate:db:export/db:import; push.ts нормализует URL через URL API (dropchannel_binding, forcesslmode=requireдля neon.tech).
Ключевое открытие: DEV_DATABASE_URL secret вопреки имени указывал на
ТЕСТОВЫЙ проект — Sorgente тест-стенда живёт в той же ротируемой тест-БД, а
не в "dev Neon" из ADR 0052. После удаления retired-проекта Sorgente падал с
password-auth — починено переводом сида на resolved URL. ⚠️ ADR 0052 при
этом НЕ обновлён — нужно решение юзера (amendment или отдельный ADR).
UI-фиксы (класс «зависимость от строки в БД» убит):
- Логотип short-form на Sorgente-странице: имя seed-проекта резолвится в
коде (
isSorgenteProject → "Arno Sorgente"), не из projects-таблицы. - Закреплённая карточка "Arno Sorgente" на странице проектов рендерится по live maintainer-пробе, без projects-row.
Egress (2026-06-19, тот же спринт): /sorgente/state poll 2s → version-token 4s + пауза скрытых вкладок + debounce refresh — ~99% срезано; квота больше не сгорает за день.
Секреты: NEON_API_KEY добавлен (rotation/backup/deploy резолв);
DATABASE_URL_TEST теперь только fallback; DEV_DATABASE_URL для
arno-api-test больше не используется (остался у arno-api-dev).
Pending:
- Удалить temp
GET /api/v1/sorgente/_debug/connизsorgente.tsпосле недели без рецидивов. - Решить судьбу ADR 0052 (test-Sorgente = rotating test DB, не dev Neon).
- Данные Sorgente на production-ветке Neon-проекта
Arno— юзер подтверждал их наличие; сейчас стенд их не читает. Открытый вопрос: нужны ли они.
Session 2026-06-15 — Layers panel UX polish (branch feat/sorg-layout)
Layers tool got a deep UX pass on top of the v1 tool. All landed on
feat/sorg-layout (shared worktree with the Punta session — see
collision note below). Verified live via Claude-in-Chrome with realistic
timing (background-tab throttling caveat — see docs/layers/architecture.md
§Resize note).
Shipped & verified:
- Multi-drag — Shift-pick set drags together;
moveNodesAt(tree-ops) preserves DFS order, strips root, dedups descendants, cycle-guards every picked container. Tests D10–D21. - Drag-hover auto-expand (spring-loaded folder, 100ms) + auto-scroll hot-zone during drag.
- Keyboard nav — Arrow up/down (+auto-pick), Arrow left/right collapse/expand-or-move, Delete/Backspace, Cmd+Z; Enter pick.
- ARIA — role=tree/treeitem, aria-level/expanded/selected, polite live region for drop target.
- Resize fade — non-root rows fade opacity to 0 between
FADE_THRESHOLD_PX(36) and MIN_WIDTH (32); pick + hover gated on expanded width. - Root-icon click — single=collapse, double=fit-to-tree (
DBL_WINDOW300ms); collapse→expand restores the PRE-collapse width; collapse from <40 restores to 100. 5-branch state machine — see architecture.md. - Width sizing rewrite — physical
width+min-inline-size:0, rAF tween 120ms ease-out withdocument.hiddensnap-guard. (Root cause of the long "fit locks to a value / reverse / abrupt" saga: CSS transition on a flex item's width doesn't reflow; logical inline-size ignored by flex-basis. Full writeup in architecture.md §Resize.) - Deselect — click a selected row clears it on BOTH layers + canvas
(new bus
requestDeselectAll→ TweakcloseInspector). Shift+click drops one id. - Esc — clears selection both surfaces + blurs focused row (kills the
:focus-visiblering that lingered on root). - Layer taxonomy documented — box/text/component canon in
docs/layers/glossary.md§"Layer taxonomy" +docs/punta/contract.md.
Deploy note: GitHub Actions was billing-blocked mid-session (org
billing); once cleared, normal push deploys. Local wrangler pages deploy
can't reach the CF account (403, no scoped token) — rely on Actions.
⚠️ Cross-session collision (2026-06-15) — d6249b0 bundled Punta's staged work
Layers and Punta share ONE worktree on feat/sorg-layout. Commit
d6249b0 (titled as a Layers width fix) ALSO contains Punta's
then-staged changes — Punta.tsx, Punta.css, docs/punta/contract.md,
docs/punta/invariants.md, Rules.md — because git commit picked up
everything in the shared index, not just the Layers paths. Nothing is
lost; the tree was green. Punta session: do NOT re-commit those edits,
they are already in d6249b0. History was left as-is (no force-push —
a live session shares this worktree). Lesson for both sessions: commit
with an explicit pathspec (git commit -- <paths>) so a shared staged
index doesn't get swept into the other's commit.
Cross-session note → Punta (2026-06-15, from the Layers session)
Layers and Punta currently share branch feat/sorg-layout. Three things
the Punta work-stream should know:
- Shared CI gate.
deploy-testrunspnpm -r typecheck+pnpm -r testacross the whole repo. A mid-flight Punta refactor that leaves any punta test uncompilable blocks the Layers deploy too, not just Punta. (Happened today: the registry split BOX/TEXT/COMPONENT leftsection-registry.test.ts/validators.test.tsred for a window.) Resolved by commitd0e2c46— tsc clean, 91 punta tests green. Keep the branch green or flag here before pushing a breaking intermediate. - Layer taxonomy is documented and canonical.
docs/layers/glossary.md§"Layer taxonomy" (box = row|column; text/component =node.type"component" split byref). Consumer mapping indocs/punta/contract.md§"Applicable sections by layer type". If you change how sections map to layer types, sync both docs. - Tree shape is shared kernel.
LayoutNode(row/column/component) lives instudio/types.ts— single source. Punta addedgapon containers (good). Any further new node field is a contract change — note it here so the Layers side (widthForTree, drop-tracker, renderer) doesn't drift.
Session 2026-06-11 / 2026-06-12 (latest) — Layers tool complete (branch feat/sorg-layout)
Sidebar panel + drag-drop + magnet pick поднят до strategic-tier инструмента (30k MAU horizon). Все три bug-репорта от пользователя закрыты на корне через gap+depth модель + auto-prune + аудит.
Что зашло:
- Bug fixes (committed early in session):
- root icon выравнен с depth-1 column, edges 8/8 left+right
- drop-to-bottom больше не уходит "предпоследним" (surface-aware over rect)
- auto-fit width учитывает что deepest visible row — leaf vs container
- ban empty layers — pruneEmptyContainers строго дропает blank Text (включая undefined / non-string / whitespace) + cascading empty containers
- gap+depth drop model — заменил dnd-kit row-zone collision своим
pointer-driven tracker'ом. Курсор Y → gap между двумя visible rows,
cursor X → depth (snapped к indent grid). Один drop-line indicator,
один parent-row tint, нет ambiguity "before/after which row".
Modules:
drop-tracker.ts,tree-ops.moveNodeAt,tree-ops.insertFromPaletteAt. - Instrumentation pipeline. Новая таблица
sorgente.studio_event(migration20260611120000_sorgente_studio_event.sql). POST/GET endpoints вapps/api/src/sorgente.ts. Client emitter (studio-events.ts) собирает drag_end / drag_cancel / prune / undo / reset события батчами 1s × 10. drag_end payload включает intent + divergence flag (post-rAF DOM check vs tracker promise) — этот класс багов "показывает в одно место, ложится в другое" теперь ловится автоматически в digest. - Capability flags scaffold —
SurfaceCapabilitiesтип на шине, 5 флагов (allowDrag/Delete/PaletteInsert/ReorderRoot/EditNodeProps), defaults all-true. Gates вшиты в StudioDndShell + LayersSidebar context menu + useStudioTree сигнатуре. Surface'ы opt-in без изменения кода Layers. - Test harness. 55 vitest unit-сценариев (tree-ops + drop-tracker
pure exports + types + studio-bus). Coverage gate активен —
vitest-config scope:
tree-ops.ts+types.ts. Thresholds: 100% lines / 100% functions / 92% branches (over 90% gate)..github/workflows/layers-tests.ymlблокирует merge при падениях / drift coverage. Каждый сценарий sourced из chat-репорта или явного spec rule — никакой фантазии. - /app/sorgente/layers-digest — страница digest с window-selector (7/14/30/90 дней). Цветовая индикация divergence rate vs threshold. Samples list для triage. Notifier 14-day click-through landing.
- Notifier pipeline. Local launchd plist
(
com.vadimpianof.arno-layers-digest, fires every 14d via StartInterval=1209600s) + shell script (~/.local/bin/arno-layers- digest.sh) добавляет Notifier card с click-through на digest URL. Загружен черезlaunchctl loadв текущей сессии. Plist + script не в git (лежат в~/Library/LaunchAgents/+~/.local/bin/). - Документация. Полный 11-файл canonical set в
docs/layers/(README / _index / architecture / contract / invariants / integration / api-reference / telemetry / testing / migration / scenarios / glossary).apps/web/src/dev-tools/studio/_index.md+studio/README.mdобновлены как umbrella manifest (studio != layers; layers — один tool внутри). Code-side_index.md+README.mdрядом с кодом — pointers на docs/. Rules.md§Studio tools — documentation pattern(parallel session) codifies the 11-file pattern repo-wide.
Параллельная работа в этой ветке (не моя, в коммитах честно помечено):
docs/nav-bar/— nav-bar tool scaffold, 11-файловая структура по тому же шаблону. Стартовая работа над следующим tool'ом.Rules.md§Studio tools — codification 11-file pattern as binding rule.apps/web/src/app/sidebar/...rail width — отдельная UI работа, не Layers.
Открытые / parked items (зафиксированы explicit в docs):
- Component tests (Layer 2) + E2E (Layer 3) — нужны при первой регрессии вне pure logic (DOM, focus, scroll, keyboard).
- 10 planned сценариев K01–K10 в
scenarios.md— требуют Layer 2/3 инфраструктуры. - Multi-select drag, extended shortcuts (Cmd+D/G/C/V), locked nodes,
i18n UI labels, visual regression Playwright — в
README.mdRoadmap. /app/sorgente/layers-digestpage визуально не проверен (deploy in flight; страница смонтирована, fetcher + thresholds готовы).
Ключевые commits (chronological):
| SHA | Что |
|---|---|
380f527 | root alignment + edge padding restore |
57bfbdc | edges 8/8 |
4095acb | surface-aware over-rect (drop-to-bottom fix) |
d6ddd29 | auto-fit width per-row |
675600d | ban empty layers — strict prune |
db05ee8 | gap+depth drop model |
b7620d7 | instrumentation + 22 unit tests + Notifier pipeline |
d32af5c | docs(layers): full 11-file documentation suite |
ff819eb | CI gate + digest page + capability flags |
ea98d9a | parallel-session docs/nav-bar + Rules.md doc pattern codification |
9323b59 | coverage gate active (95% lines / 90% branches / 100% functions) |
Sign-off 4 оси:
- Компактно: 1 контракт (studio-bus), 1 source-of-truth для типов, без forks
- Документировано:
docs/layers/11 файлов + _index/README рядом с кодом - Наблюдаемо:
studio_eventtable + digest endpoint + страница + Notifier таймер - Тестируемо: 55/55 unit pass, CI gate активен, coverage 100%/92%/100%
Layers tool готов. Next session — другие studio tools (nav-bar в скаффолдинге; renderer / palette / inspector в roadmap).
Session 2026-06-08 — Sorgente editor surface complete
Built on top of ADR 0052 architecture (see entry below). /app/sorgente
is now a full CRUD + audit editor for the shared design system:
- Tokens — 98 token overrides (ported from globals.css :root via
tools/port-v9-synth-tokens). Each row: label + color swatch + value input + Apply / Reset / × Delete. AddTokenForm at top for new rows. Live optimistic preview via SorgenteOverlay polling. - Brand → Seeds — BrandSeedField per slot (picker + hex + label) + AddSeedForm. Editing hex regenerates dependent ramp downstream.
- Brand → Bindings — BrandBindingField (semantic id → slot + step dropdowns) + AddBindingForm. Graceful empty-state when no seeds yet.
- History — apply_log feed from server (when / table / row / env) + Revert button per row that re-plays prev_value through the matching endpoint (UPDATE → restore, CREATE → delete, DELETE → recreate). Refresh button. Every revert creates its own apply_log row so chains walk the audit history.
Architecture:
dev-tools/sorgente-draft/use-sorgente-draft.tsx— SorgenteStateProvider (single owner of /sorgente/state 2s polling) +useSorgenteDraft<T>({ kind, id })generic hook. Optimistic overlay-merge so drafts stage instantly across all consumers of the same target before Apply lands.dev-tools/sorgente-draft/fields/{TokenField,BrandSeedField, BrandBindingField,AddForms}.tsx— composable presentational components, all wired through the same hook. No registry / spec file — new fields = new component import + JSX usage.
Tests:
- 75 new vitest cases in
apps/api/src/sorgente.test.tscovering TOKEN_ID_RE / SLOT_RE / SEMANTIC_ID_RE / STEP_VALUES / HEX_RE validation regex (positive + negative + boundary) and gating smoke tests (unauthenticated → 401, no SORGENTE_DATABASE_URL → 503). - Validation regex exported from sorgente.ts so the server-side contract is pinned for tests; AddForms / useSorgenteDraft mirror the same shape on the client.
- Total api tests: 140/140 green. Repo typecheck 17/17 green.
Live on test stand:
- https://test-foundation-v1-tokens.arnomake.com/app/sorgente (opens in a new tab)
- Schema applied on dev Neon (sorgente namespace + 9 tables)
- SORGENTE_DATABASE_URL seeded on test + dev workers (NOT prod per ADR 0052 §2)
- 98 v9-synth tokens in sorgente.token_override
What's NOT built yet (next round candidates):
- LayoutInspector / SpacingInspector / BrandPanel rewrite onto useSorgenteDraft. Hook + schema ready; rewrite is gated on a product UX decision (per-element CSS override vs editing the token the element consumes). Maintainer iterates on /app/sorgente directly today.
- Composite token support (typography / shadow). Scalar-only path covers everything ported from globals.css; composite values are silently skipped in the export.
- Group Apply transaction (ADR 0052 §3 "Apply all pending in this panel"). Per-row Apply is the only path today.
- Variant editor (ADR 0044) — schema sorgente.variant_override ready, no edit UI.
- Prod release. Squash-merge feat → main +
release (prod)runs the globals.css regenerator and ships the static CSS bake.
Session 2026-06-08 (late) — ADR 0052 supersedes per-env Sorgente (branch feat/foundation-v1-tokens)
Earlier today this branch carried per-env Sorgente (Phase A+B + Rounds 1-6, see prior entry below). After live trial that architecture turned out wrong for the actual maintainer flow — every edit required a Publish click and per-env sync workflows, but the maintainer needs ONE design system surfaced everywhere, edited instantly from any project. ADR 0052 (docs/adr/0052-sorgente-shared-db.md) replaces the per-env DB model with shared-DB. The product intent of ADR 0049 stays; only the storage layout changes.
New architecture (см. ADR 0052 §1-§4):
sorgenteschema in dev Neon = single source of truth for brand/variant/composition state- All test + dev workers read+write via
SORGENTE_DATABASE_URLsecret (= dev Neon URL) - Prod has no such secret; live Sorgente endpoints fail closed (503). Prod ships only the static CSS bake from the last
release (prod) - Apply gate at LayoutInspector / SpacingInspector / BrandPanel — local React draft, explicit Apply commits to DB, Reset reverts to last applied
- Propagation = 2s polling loop in
SorgenteOverlay(mounted in /app/layout) - No Publish/Pull buttons. No promote/sync workflows. State is already shared.
Landed in this session:
- ADR 0052 written (docs/adr/0052-sorgente-shared-db.md)
- Migration
20260608110000_sorgente_schema.sql—CREATE SCHEMA sorgente+ 9 tables (brand_seed/binding/font/icon/token_override/wcag_override + variant_override + composition_instance + apply_log). All keyed by user_id. - Migration
20260608120000_drop_legacy_sorgente.sql— dropsproject.is_arno_sorgentecolumn + partial unique index +arno_sorgente_publish_logtable. Per-env Sorgente concept gone. packages/db/src/schema.ts— added 9sorgente.*exports viapgSchema. Dropped per-env legacy schema.apps/api/src/sorgente.ts— new endpoints:GET /sorgente/state, PUT/DELETE per-table Apply endpoints (token-override, brand-seed, brand-binding),GET /sorgente/apply-log. Maintainer gate viaauth-allowlist.isLoginAllowed(env, requestUserLogin)+ presence ofSORGENTE_DATABASE_URL.apps/web/src/components/sorgente-overlay.tsx— rewrote on/sorgente/statepolling.apps/web/src/app/app/sorgente/page.tsx— strip UI (no project_id binding); read-only tokens + brand inventory. Inspector wiring lands as follow-up.apps/web/src/lib/api-client.ts— newfetchSorgenteState/applySorgenteTokenOverride/applySorgenteBrandSeed/applySorgenteBrandBinding/fetchSorgenteApplyLog+ delete helpers. OldpublishArnoSorgente/syncArnoSorgenteFromUpstreamdeleted.tools/port-v9-synth-tokens/— one-shot port: parsesapps/web/src/app/globals.css:root { ... }block, upserts each var intosorgente.token_overridekeyed by maintainer's user_id.tools/export-arno-sorgente/run.ts— rewritten as globals.css regenerator (readssorgente.token_overridefor maintainer, replaces:rootblock in-place between/* ===== sorgente:auto-generated:begin ===== */markers).release.yml— export step now usesSORGENTE_DATABASE_URL(= dev Neon) +MAINTAINER_LOGIN=vadimpianov. Commits the regenerated globals.css if changed.deploy-test.yml+ci.yml— per-env Sorgente export steps removed. Test/dev rely onSorgenteOverlaypolling at runtime, no rebuild needed for chrome to reflect Sorgente edits.apps/web/src/app/app/library/page.tsx— removedPublishSorgenteButton+isArnoSorgentelookup.apps/api/src/index.ts—/api/v1/me/projectsresponse no longer carriesisArnoSorgente.- New workflow
.github/workflows/apply-sorgente-schema.yml— one-shot applies both new migrations (20260608110000_sorgente_schema+20260608120000_drop_legacy_sorgente) to dev Neon. - New workflow
.github/workflows/port-v9-synth-tokens.yml— one-shot runs the port script.
Removed (in same commit, per ADR 0052 §8):
.github/workflows/{arno-sorgente-promote,arno-sorgente-publish,seed-arno-sorgente-from-prod,seed-gh-dispatch-token,apply-arno-sorgente-migrations}.ymlapps/api/src/arno-sorgente.ts+arno-sorgente.test.tsapps/web/src/components/publish-sorgente-button.tsx
Tests: 65/65 api tests + 17/17 typecheck зелёные.
Pickup sequence next session (in order):
- Squash-merge this branch to main (no Publish/Pull/per-env infra is left to migrate; this is a coherent replacement).
- Run
apply-sorgente-schema.ymlagainst dev Neon to create the new schema + drop legacy columns. After success:git rmit. - Sign in once on
dev.arnomake.comso thevadimpianovuser row exists on dev Neon (likely already does from prior sessions). - Run
port-v9-synth-tokens.ymlwithlogin=vadimpianov. After success:git rmit. dev Neon now has every globals.css:rootvar assorgente.token_overriderows. - Set
SORGENTE_DATABASE_URL = $DEV_DATABASE_URLon the prod worker only if you want the maintainer Sorgente UI accessible there — by ADR 0052 §2 prod should NOT have it. Skip. - Set
SORGENTE_DATABASE_URL = $DEV_DATABASE_URLon the test + dev workers. Pattern: copyseed-pulse-read-token.ymlas a one-shot orwrangler secret put SORGENTE_DATABASE_URL -c wrangler.dev.toml/.test.tomllocally. - Push to feat (or merge to main) → next deploy of test/dev workers reads sorgente.* live.
- Verify on
dev.arnomake.com/app/sorgente+test-foundation-v1-tokens.arnomake.com/app/sorgente: strip UI shows currentsorgente.token_overrideinventory. SorgenteOverlay paints:rootoverrides over globals.css. Edit a token via Apply (when inspector wiring lands) → all open editor tabs in both envs show within 2s. - Follow-up PRs (not in this commit):
- LayoutInspector + SpacingInspector + BrandPanel — Apply / Reset gate wired to the new endpoints. Schema is ready; this is pure UI work.
- Snapshot chain rebuild for drizzle-kit generate (the pre-existing collision is still there; hand-authored migrations remain the active pattern).
- Rebuild
arno_sorgente_publish_logreferences in HANDOFF if any external scripts still query it.
Known caveats:
- Test stand frontend will keep showing the static globals.css for non-
/app/sorgentechrome until a maintainer Apply via inspector overrides individual tokens. Initial paint = whatever globals.css ships; SorgenteOverlay paints over within 2s. - ADR 0049 Rounds 1-6 infrastructure has been historically preserved in git log (commits
663cdd7…fdbd55b); no rollback needed since the new architecture supersedes cleanly.
Session 2026-06-08 — Arno Sorgente Phase A + B (branch feat/foundation-v1-tokens)
ADR 0049 phases A and B landed together on this branch. The v9-synth design
direction is renamed to Arno Sorgente in every non-historical surface
(globals.css header, design-system-legacy.html title/chips, mocks/_index,
foundation-catalog-v9.css → foundation-catalog-arno-sorgente.css; ADR 0030
left untouched per Rules §Language ADR carve-out; mock HTML filenames
kept for chronology with a rename note in mocks/_index.md). The reserved
project id prj-arno-sorgente was DROPPED — the live Sorgente row on prod
is the existing prj-by3zajnv flipped to is_arno_sorgente = true, which
preserves every FK across captured_page / brand_seed / brand_binding /
token_override. Routes resolve by flag, not by id.
Schema (packages/db/src/schema.ts):
project.is_arno_sorgente boolean NOT NULL DEFAULT false+ partial unique indexproject_arno_sorgente_singleton ON (is_arno_sorgente) WHERE is_arno_sorgente = true— at most one Sorgente row per env.arno_sorgente_publish_logtable — audit row per Publish press (status, source_snapshot_hash, workflow_run_id, deploy_commit_sha, error_message, completed_at).
Migrations: 20260608090000_arno_sorgente_init.sql and
20260608090100_arno_sorgente_publish_log.sql hand-authored (drizzle-kit
generate is broken on this branch — two existing snapshots share the same
prevId, pre-existing not introduced here). Journal entries idx 29 and 30
added; no snapshot files written — apply via the one-shot workflow path,
not via migrate push on test/dev.
Static export (tools/export-arno-sorgente/): new workspace package
@arno/export-arno-sorgente. tsx run.ts reads the Sorgente project's
brand_seed / brand_binding / brand_font / brand_icon / token_override /
wcag_override rows, resolves bindings through generateRamp from
@arno/foundation/brand/hct-ramp (new export entry; bypasses the React
components barrel so the node script doesn't try to import .css files),
emits packages/foundation/dist/arno-sorgente.{css,json,meta.json}.
Idempotent: skips writes when the resolved content sha256 matches the
prior meta.json hash. Composite tokens (typography, shadow) recorded in
JSON but NOT emitted to CSS in Phase A — scalar-only. Gitignore for
packages/foundation/dist/ keeps blanket dist/ ignore + explicit
un-ignore for the three Sorgente files (release.yml diff-and-commit
expects them visible).
Static layer wired: apps/web/src/app/layout.tsx now imports
@arno/foundation/tokens.css then @arno/foundation/arno-sorgente.css
at root, before ./globals.css. Order matters — Sorgente overrides
Foundation. Initial dist/arno-sorgente.css committed as the empty
placeholder (:root {}) until the first prod release regenerates it.
Publish endpoint (apps/api/src/arno-sorgente.ts): two routes mounted
under requireAuth, both maintainer-gated by ownerId === userId on the
Sorgente row (no separate role table — flipping is_arno_sorgente is
itself maintainer-only):
POST /api/v1/projects/arno-sorgente/publish— snapshots state to sha256, insertspendingrow inarno_sorgente_publish_log, dispatchesarno-sorgente-publish.ymlvia GitHubworkflow_dispatchusingGH_DISPATCH_TOKENwrangler secret. ReturnspublishLogIdfor client polling.GET /api/v1/projects/arno-sorgente/publish-log?limit=N— history pane source. Used by the button below for status polling and by the future revert UI.
Pure helpers canonicalSnapshot + snapshotHashFromCanonical exported
for tests (6 new vitest cases — empty stability, order invariance,
revoked WCAG drops, audit column dropping). Workflow updates the log row
directly via DATABASE_URL rather than calling back over HTTP — fewer
auth surfaces, fewer round-trips.
UI: apps/web/src/components/publish-sorgente-button.tsx —
auto-polling button in the Library navbar that only renders when the
loaded project has isArnoSorgente === true. Status surfaces:
Publish to source → Publishing… → Published <sha7> /
No changes / Publish failed. Project list endpoint
(/api/v1/me/projects) now exposes isArnoSorgente so the Library
page hydrates the flag from the same fetch as dsMode.
Route /app/sorgente: client-side resolver — fetches projects,
finds the isArnoSorgente === true row, redirects to
/app/library?project=<id>. Auth-gated via the /app/** layout's
existing AuthGate. Spec §2 route preserved; reserved-id assumption
dropped.
Workflows:
.github/workflows/release.yml— addedpermissions: contents:write- checkout token + two new steps (
Export Arno Sorgente snapshotandCommit Arno Sorgente snapshot if changed) BEFORE the Storybook + web build (the build embeds arno-sorgente.css; running export after the build would ship a stale skin).
- checkout token + two new steps (
.github/workflows/arno-sorgente-publish.yml—workflow_dispatchtarget for the Publish endpoint. Inputs:publish_log_id. Steps: mark row as in-flight → export → commit-if-diff → mark row terminal (status + commit sha) vianode --input-type=moduleinline scripts.concurrency: arno-sorgente-publishserialises operator double-clicks..github/workflows/apply-arno-sorgente-migrations.yml— one-shot for test + dev, mirrorsapply-pulse-event-migration.ymlshape. Applies the two new migrations directly and records them indrizzle.__drizzle_migrationsso a futuremigrate pushskips them..github/workflows/seed-arno-sorgente-from-prod.yml— one-shot. Reads brand state from prod'sprj-by3zajnvviaPROD_DATABASE_URL, upserts toprj-arno-sorgente-seedon the target env, flipsis_arno_sorgente = true. Owner resolved by thetarget_owner_logininput lookup against the target env'susertable. Re-runnable: each invocation DELETEs + re-INSERTs the brand rows on the target.
Pickup sequence next session:
- Merge ordering — this branch (
feat/foundation-v1-tokens) carries schema.ts changes; rebase on main as soon as anti-spy lands so the conflict surface stays small. - Run
apply-arno-sorgente-migrations.ymlagainst dev + test (both). - Set
GH_DISPATCH_TOKENon dev + test workers (wrangler secret put GH_DISPATCH_TOKEN -c wrangler.dev.tomland.test.toml; fine-scoped PAT withrepo:actions:writeonArnomake/arno). Prod gets the same secret when this branch ships. - Run
seed-arno-sorgente-from-prod.ymlwithtarget=test,target_owner_login=vadimpianov. Sign in once ontest-foundation-v1-tokens.arnomake.comfirst so the user row exists. - Verify on
test-foundation-v1-tokens.arnomake.com/app/sorgente→ should redirect to the seeded project's library page, with the "Publish to source" button visible in the navbar. - Squash-merge → main. Release pipeline picks up:
- the two migrations (
pnpm migrate push) - the export step (regenerates
arno-sorgente.csson every prod release) - the commit-if-diff step (any new prod brand-seed edits land in git automatically).
- the two migrations (
- Set
GH_DISPATCH_TOKENon the prod worker. Flip prod'sprj-by3zajnvrow:UPDATE project SET is_arno_sorgente = true WHERE id = 'prj-by3zajnv';(one-shot SQL via a temporary workflow, per the established no-direct-prod pattern). - After two clean releases without drift surprises → drop the two
one-shot workflow files (
apply-arno-sorgente-migrations.yml,seed-arno-sorgente-from-prod.yml).
Known caveats:
- Drizzle-kit generate is broken on this branch (snapshot prevId collision). Hand-author all new migrations until the chain is rebuilt (a one-shot drizzle snapshot rebuild lands as a separate small PR after this branch merges).
- The 5 files CONFLICT with PR #45 listed in the prior session are STILL conflicted by my schema.ts edits — they need to be aligned before merge. The migrations themselves are additive (new column, new table), so the SQL files do not conflict.
packages/foundation/dist/arno-sorgente.cssis committed initially as empty (:root {}). First prod release withis_arno_sorgenteflipped regenerates it from prod's brand state. Until then the static layer is a no-op cascade entry (the runtime overlay in Phase D will bring live values for the maintainer).
📦 Старые сессии (2026-06-07 и раньше) вынесены в
HANDOFF-archive.mdчтобы не грузить контекст каждой новой сессии. Открывай архив только если нужна история до 2026-06-08.
TL;DR
Phase 0-14 🟢 done. MVP scope §IV почти полностью покрыт. Live + работает.
Layout Grid Phase 1-3 🟢 done (session 2026-05-25..27, см section ниже).
Composition zones unification 🟢 done (session 2026-05-27 late — ADR 0027/0028, см ниже).
Backend prod deploy + auto-migrate в CI 🟢 done (ADR 0029 — solo-dev deviation от master spec §III.2.5). Требуется: один раз set GH Actions secret PROD_DATABASE_URL (см "One-time CI setup checklist" ниже).
Phase 15-16 осталось: Onboarding → Production readiness (~8 дней).
🎯 2026-06-02 — Mantine capture milestone (live verify, test-capture-v2.arnomake.com project 2):
- Captured
https://mantine.dev/core/button/→ 411 leaves / 263 containers / depth 14, single capture - DS grid 48 components — Button/Icon/Text семантика чистая (включая весь sidebar Mantine: AppShell, AspectRatio, Center, Container, Flex, Grid, Group, SimpleGrid, Space, Stack, AlphaSlider..MaskInput — visual эффект «забрал все subpages» за счёт того что Mantine выкладывает sidebar как plain
<a>, DOM-walk их подбирает) - Workflow reassembly: единый srcdoc 1.36 MB → visual fidelity ≈ 9/10 (vs Stripe 2-3/10, GitHub 6-7/10 в measurement pass). Header + sub-nav + sidebar + main content (huge Button heading, meta-row, 3 tabs) — пиксель-в-пиксель. Остаточный косяк: 2 пары item'ов в сайдбаре налезают на ~5px (position-context, добиваем в P1 если потребуется)
- Fonts: Outfit (Google, weights
100/100 900/900) — верно классифицировано - Tokens: 8 extracted. Fidelity deviation: 1 (off dominant scale). Снап-к-pattern toggle = ON
- Vindicates capture-v2 P1 stack: animation pause+seek
5eb1a8c+ opacity:0 dropae5d988+ layout cap raise402e8e5+ positioning contextdbf59c7+ step 1-9D pipeline (real text/SVG/data-URI images, container rects+paint order, force-show animation targets, nested DOM render с parent-relative coords, @font-face injection, ::before/::after) — все шаги видны в результате ИСПРАВЛЕНО (re-verify 2026-06-02, та же ссылка): capture перезахвачен с capture-v3 —fiber_tree = nullна этом capturefiber_treeприсутствует: React 19.2.6, 2921 компонентов / 208 уникальных / 122 типа / depth 87; 2330 инстансов несут реальные@mantine/core/*имена (Box 818, UnstyledButton 152, Code 98, Text 84, Transition 71, Tooltip 55, ScrollAreaScrollbar 90…), шум только 222Anonymous+ минифицированныеu/v. Composition diag-бар: captured 2921 / visible 2353 / with-rect 1739 / rectless 614 / framework-filtered 568. Tokens (актуальные): 424 CSS vars + 20 colors, deviations=0. Полный measurement log → docs/capture_v2_measurement.md §«Actual capture #7». Прежняя заметка «null» относилась к до-v3 снимку (single DOM-walk); B2 tag+role heuristic остаётся валидным fallback для не-React сайтов- Investor demo proof-by-example: Mantine = clean DS docs class → one-click → recognizable component catalog + faithful reassembly. До этого момента это был «должно сработать», теперь — рабочая ссылка
Capture onboarding + page reconstruction (ADR 0030) — A + B1 + B2 + B4-basic + B5 (full font subsystem) 🟢 done. Canonical = feat/layout-grid.
Шрифты (B5) закрыты: B5a Google-link, B5b-1 detect, B5b-2/3 BYOF upload+embed, Font-Phase-A local-first (device fonts via local()), A2 replace-picker. Deploy-gate (бывш. «B») осознанно отложен (нет публичной редистрибуции — см ниже). Text-edit gating (C) — позже. Verified live: до B5b-1 (2026-05-28); B5b-2/3+A+A2 задеплоены, в браузере под юзером пока не прогнаны (pending).
DONE (всё в каноне, верифицировано вживую на preview):
- Convergence: оба трека (capture/extension + layout-grid) сведены на
feat/layout-grid, дрейф с main = 0, CI зелёный (починены 4 бага пайплайна). См "MERGE 2026-05-28" ниже. - A — Onboarding: create → "Browser capture" →
entry_mode='capture'→ bridge-binding (arno_capture_project+arno-set-projectevent = мгновенное обновление) → entry_mode-aware sidebar (Library + Workflow ++меню). Extension env-aware (prod +*.arno-1hu.pages.dev) viaSET_ENV. - B1 capture:
lib/find-layouts.jsbuildLayoutTree()— рекурсивное дерево (container|leaf + layoutCss + запечённые computed-стили в leaf html + node tokens). Verified: 396 узлов на liveblocks. - B2-backend:
captured_pageтаблица +POST/GET /api/v1/captured-pages(ownership) + extensionUPLOAD_PAGE_TREE. - B2-frontend: Design System рендерит реальные leaf-компоненты (
captured-library.tsx), fallback на legacy-мок если нет captured-страниц. - B4-basic: Workflow рендерит пересобранную страницу из дерева (
reconstructed-page.tsx, sandboxed iframe), fallback на граф. +<base>для ассетов. - B5a fonts: Google-шрифты линкуются по имени в reassembly; Apple/системные — через system stack.
- B5b-1 fonts:
detectFonts()классифицирует used-шрифты →captured_page.fonts(google|byof, фильтр фолбэков, реклассификация популярных Google). Verified: liveblocks → Suisse[byof], Inter/JetBrains[google].
B5b-2/3 — BYOF upload + embed — КОД ГОТОВ (session 2026-05-28), pending deploy+verify.
- DB:
project_font(base64-байты в Postgres — R2-биндинга нет; MVP-блобстор) + миграция20260528143232_project_font_byof.sql. Миграция на test-Neon ещё НЕ применена (нужен manual node+@neondatabase/serverless — journal divergent). - API
apps/api/src/fonts.ts:POST/GET /api/v1/projects/:id/fonts,GET .../fonts/faces(data-URI для embed),DELETE /api/v1/fonts/:id. license_attested обязателен; size-guard ~3MB. - web: вкладка Fonts в
/app/library(fonts-panel.tsx) — группы google/system/byof, drag-drop, license-чекбокс, OpenType auto-map (lib/opentype-name.ts: name-таблица для ttf/otf — verified 8/8 на системных шрифтах; woff/woff2 → эвристика по имени, brotli в браузере не распаковать). - embed:
@font-facedata-URI в srcdoc reassembly (reconstructed-page.tsx, все faces) + grid (captured-library.tsx, только faces семейства листа). Хелперlib/font-faces.ts. - typecheck зелёный (web/api/db), web-lint чист, docs (rest.md/schema.md) регенерированы.
- Задеплоено + проверено (2026-05-28): commit
56c5213запушен в канон, CIdeploy-testзелёный. Миграцияproject_fontприменена на test-Neon (11 колонок verified, через drizzlegetDB+sql.raw). Fonts-роуты смонтированы (/fonts→ 401 на unauth = auth-gate до DB). Frontend-бандл содержит Fonts-таб (verified в чанкеpage-eab09c22). - ⚠️ Тест-стенды (trunk-based, 2026-05-29):
dev.arnomake.com(последний main),test-<topic>.arnomake.com(превью ветки через proxy-worker). Старыеfeat-layout-grid*.pages.devURL мертвы — ветка удалена. - Capture-v3 — code extraction pipeline (2026-06-01, ветка
feat/capture-v2): strategic pivot от visual DOM-walk reassembly (capture-v2 P1+P2 patches) к React fiber introspection + TSX code generation. Не регрессировать этот flow — это база под investor demo за 5 дней.- Phase 1 (commit
4670680) — fiber probe runs in page MAIN world viachrome.scripting.executeScript({world:"MAIN"}). Walks__reactFiber*/__reactInternalInstance*/__reactContainer*from any DOM node, emits flat list{id, name, typeId, parentId, props, depth}with stable per-type identity. Stored ascaptured_page.fiber_tree jsonb(migration20260601000000_captured_page_fiber_tree.sql, applied на test-Neon). - Phase 2 (commit
d403300) —apps/web/src/lib/tsx-generator.tspure-functional pipeline.clusterByTypeгруппирует instances поfiber.typeidentity;inferPropsTypeunions observed types в TS interface (optional marker если prop не на каждом instance);generateComponentTsxemit functional shell. Sentinels из probe (<children>/<fn>/<array[N]>/<object>) map toReact.ReactNode/function/unknown[]. Visible в Code tab новый раздел "Generated TSX (top 20)". - Phase 3 (commit
5c73d0e) —generatePageCompositionwalks parent/child graph top-down, emits singlePage.tsxfile: import block per distinct component + JSX assembly. Sentinels render asprop={/* placeholder */}comments. CapsPAGE_NODE_CAP=2000/PAGE_DEPTH_CAP=80. - Phase 3 hotfixes (
ff458b2,584e62b) — skipContext.Provider/Consumer/forwardRef/memo/lazy/fragment$$typeof noise + name-shape heuristicisFrameworkNoise(length ≤ 3, all-caps ≤ 4 chars, "Anonymous"/"Special"/"Component" fallbacks). Flatten through them: их children promote to parent's level. Empty-string filter + self-close when all kids were noise. Stripe: 1557 instances → 78 user nodes, 527 framework filtered. - Phase 4 (commit
188b0fd) — WorkflowCompositionview default mode когда fiber_tree present.components/composition-renderer.tsxrenders nested labelled boxes (legacy fallback) + right side panel с TSX skeleton on click. Mode toggleComposition / Reassemblyв header — old DOM-walk reassembly остаётся как legacy fallback. - Phase 5 (commit
b431c92) — spatial composition: extensionextractFiberInPageдля каждого fiber находит first host descendant (stateNode.nodeType===1или recurse children with budget=200), samplesgetBoundingClientRect+ computed{bg, color, br, fz, fw}. FrontendLayoutBoxрендерит absolute-positioned box на page-coords canvas с real surface CSS. Hover/selected → label tag floats above. Pre-Phase-5 captures (без rect) fall back на nested view. - Ceiling honest: spatial layout работает для видимых компонентов; animation-hidden / portals / fragments / shadow-DOM teleport → null rect → не появятся на canvas. Animation-heavy SPA (Stripe-class) показывают только небольшой видимый slice. Visual fidelity для animated SPA fundamentally limited without runtime compile + real component bodies — это Phase 6+ territory (Sandpack / esbuild-wasm).
- Demo-ready state: capture on Mantine/docs/admin sites → fiber tree clean (95% real names), composition view useful. Stripe-class → partial. Side-panel TSX inspector работает на любых captures. Generated TSX и Page.tsx уже копируется и компилируется как самостоятельный React project (placeholder bodies).
- Phase 1 (commit
- Extension capture-onboarding bind flow (восстановлен 2026-05-31, серия фиксов
1ef4f65..abfe9a5): после trunk-based cutover bridge на новых стендах не привязывал проекты — capture лил в legacy/app/imports/. Сейчас полный поток работает; не регрессировать при измененииpackages/url-import-extension/:- manifest.json
content_scripts.matchesпокрывает:arnomake.com/app/*,*.arnomake.com/app/*(включаяdev.*иtest-<topic>.*),*.arno-1hu.pages.dev/app/*(legacy). Сужать без проверки списка живых хостов — гарантировано сломать bind. - manifest.json permission
webNavigationобязателен — без него background не ловит SPA pushState из Next.js App Router → переключение проекта в тех же tab не обновляет bind. - arno-bridge.js env-detection (
isProd):dev.arnomake.comпомечен как prod (frontend ходит на prodapi.arnomake.com, проверено в bundle chunks). Толькоtest-<topic>.arnomake.com+ legacy CF Pages →arno-api-test. Несогласованность frontend↔bridge → JWT issued одним backend, validated другим → "Token invalid". - arno-bridge.js bind sources (три слоя): (1)
localStorage.arno_capture_projectflag — для свежесозданных через "+ New project" → "Browser capture"; (2)?project=prj-XXXв URL — ловит открытие существующего проекта; (3)arno-set-projectcustom event от React — real-time push без reload. Все три обязательны: убрать (2) → bind не работает для existing projects; убрать (3) → real-time switching не работает. - SPA navigation re-bind:
chrome.webNavigation.onHistoryStateUpdatedв background.js пингует bridge черезCHECK_URLmessage при изменении URL без полной перезагрузки. Plus bridge сам слушаетfocus+visibilitychangeкак cheap fallback. Content-scripthistory.pushStatemonkey-patch не работает (isolated world), не пытайся. - Project name resolution race:
SET_PROJECT_IDот bridge может прийти раньшеAUTH_TOKEN_HANDOFF(JWT ещё не в storage). HandlerresolveAndBindProjectсразу пишет id-as-name fallback и ретраит fetch/api/v1/me/projectsдо 5 раз с 1s spacing. Убрать retry → popup застрянет наprj-XXXвместо имени. - "Extension context invalidated" silenced: при reload extension stale content-script на открытых страницах теряет связь с SW. Любой sendMessage throws этот error — bridge молча его глотает (один narrow
ifв setProject). Это dev-loop noise, не баг; не возвращай warn без причины. - Dogfood-loop требует ручной reload: изменения в
packages/url-import-extension/**→ юзеруchrome://extensionsreload + Cmd+R на открытых табах ARNO. Без второго шага старый bridge будет сыпать "context invalidated" в Errors. См spawn'нутую задачу проbuild:devscript (HANDOFF chip 2026-05-31) — long-term: грузить изdist/без_index.md-блокера + watch-mode.
- manifest.json
- Осталось только интерактивно (нужна авторизованная сессия юзера): sign-in → capture страницы с кастомным шрифтом (Suisse) → вкладка Fonts в
/app/library→ upload.otf/.ttf(auto-map family/weight) + license-чекбокс → проверить@font-faceв reassembly (Workflow) и grid (Design System).
МОДЕЛЬ ПЕРЕСМОТРЕНА на Figma-style local-first (session 2026-05-29, ADR 0030 §6 update). Юзер: «не грузить наперёд — подтягивать установленные шрифты, лицензия только перед деплоем».
- Phase A done (commit
1d04c40, в каноне):lib/local-fonts.ts— кроссбраузерный probe (FontFace+local(), без permission) +queryLocalFonts()enumerate (Chromium-only) +localFaceCss(). Панель Fonts: кастомные шрифты → статус On your computer / Provided for deploy / Missing; upload переименован в «provide for publishing» (не upfront). Reassembly+grid рендерят установленные шрифты черезlocal(), если нет upload. Render-приоритет: uploaded data-URI → local() → Google link → system. - Phase A2 done (commit
a496ed9): replace-picker.captured_page.font_overridesjsonb (миграция применена на test-Neon) +PATCH /api/v1/captured-pages/:id. Панель: Missing → дропдаун Replace (free-шрифты ∪ локальные черезqueryLocalFontsпо клику-жесту) + Clear; статус Replaced · → X. Рендер переписываетfont-familyв токенах И в запечённом leaf-html (overrideStack/overrideHtmlFonts) → замена реально грузится. FREE_FONTS список вfonts-panel.tsx. - Phase B (deploy-gate) — ОТЛОЖЕН (решение 2026-05-29, ADR 0030 §6). Аудит: публичной редистрибуции шрифтов нигде нет (embed только в редакторе у владельца; share-viewer рендерит только workflow-канвас; deploy пушит layout-JSON без рендерера — ADR 0028). Лицензия уже собирается на upload. Механизм гейта готов (
project_font+/fonts/faces). Re-open триггер: публично-рендерящийся артефакт начнёт встраивать шрифты (share-viewer покажет reassembly ИЛИ deploy-JSON получит рендерер по ADR 0028). - Phase C (later): текст-редактор captured-страниц + блокировка правки при отсутствии шрифта. Архитектура держится совместимой.
- Ключевое (браузерные ограничения): рендер установленным шрифтом + детект «есть/нет» = все браузеры (
local()/FontFace); полный enumerated-список = только Chrome/Edge desktop (queryLocalFonts, WICG, не стандарт). Figma делает кроссбраузерный список через helper-агент — нам для MVP не нужен.
NEXT (всё спроектировано в ADR 0030 §6/§7, атомизация B5/B6):
- Capture v2 measurement pass — done (4 сайта: mantine/github/react.dev/stripe + vercel). Полный трейл → docs/capture_v2_measurement.md §«Actual capture results» + §«Target A/B». Итоги:
- Target A (fiber-проба брала только 1 корень → 0 на island/RSC) — ЗАКРЫТ, верифицирован. Фикс
findRoots(multi-root) в main: github 0→1061, vercel →1136, react.dev без регресса. См §«Target A — RESOLVED». - Target B (fidelity под анимацией) — в работе. Reassembly ломается по 2 осям: media (canvas/lazy-img пустые) и structure/flow (горизонталь + наезды).
- B-white canvas — done+verified (
2417fb0find-layouts.js rasterizeCanvas): vercel hero рендерится. B-white pass 2 (img→drawImage→data-URI) — pending (8 next/image боксов белые). - B-structure — ДОМИНИРУЮЩИЙ, не начат. Горизонтальная раскладка: container-detection хранит только flex/grid, дропает
display:block-обёртки → теряется вертикальный document-flow. Правкаfind-layouts.js, рискованная → нужен diagnostic (top-level layoutCss) + no-regress на 4 верифицированных сайтах. Следующий заход начинать отсюда.
- B-white canvas — done+verified (
- B3 (inferential token detection) — подтверждённо low-priority (deviations=0 на всех 4, токены solved где есть CSS vars). B6 — legal floor, не блокирует (runtime fiber ≠ source code).
- ⚙️ Дев-цикл расширения: правка
packages/url-import-extension/**→pnpm --filter @arno/url-import-extension watch:dev(вотчит source → пересобираетdist/→dev-reloaderавто-reload). MV3 SW может спать → если не подхватил,chrome://extensions↻ вручную.
- Target A (fiber-проба брала только 1 корень → 0 на island/RSC) — ЗАКРЫТ, верифицирован. Фикс
- B3 — pattern detection → Fidelity deviation-list в Design System + snap выбросов на паттерн в reassembly. Возможно отменится после measurement: если ground-truth источники (framework introspection / CSS custom properties / Tailwind classes) покрывают tokens детерминированно, inferential pattern detection (B3) становится strictly tier-4 fallback.
- B6 — ownership verification (§7): account-token + extension-checked
<meta>/well-known + DNS TXT для коммерческих доменов. Двигается перед capture v2 если v2 начнёт читать чужой source (sourcemaps, vendor lib paths) — legal floor сначала. - мелочь: Google-шрифты в DS-гриде (тривиально), Storybook in-app stories, имя проекта в library/workflow UI.
ГДЕ РАБОТАТЬ (trunk-based — cutover 2026-05-29, см Rules.md): main = единственный trunk. Все долгоживущие ветки (feat/layout-grid, feat/capture-onboarding, ui-claude2) УДАЛЕНЫ. Новая работа: git worktree add -b feat/<topic> ../Arno-<topic> origin/main → PR → squash-merge → удалить ветку+worktree. Бэкап старой capture-ветки: origin/backup/capture-onboarding (контент уже в main).
ТЕСТ-СТЕНД (trunk-based): dev.arnomake.com = последний main; test-<topic>.arnomake.com = превью ветки (привязать хост: infra/preview-proxy/attach-branch.sh <topic>). Backend arno-api-test, тест-Neon ветка feat-layout-grid (host ep-holy-resonance-al2n98xv; миграции вручную через node+@neondatabase/serverless). Расширение: load unpacked из packages/url-import-extension, env auto via bridge.
Спека: docs/adr/0030-page-reconstruction.md. API/schema: docs/api/rest.md (opens in a new tab), docs/api/schema.md (opens in a new tab).
🚨 До любых действий в новой сессии: прочитать секцию "Security debt" ниже — production secrets (CF-токен
arno-test-deploy, prod DB-пароль) утекли через chat, требуется ротация.
Side feature: URL-import (master spec v1.3 §V unparked) — architecturally complete, parked для V1 launch decision:
- Pipeline (Phase 1+5+6+10): packages/url-import-extractor — CLI works on real sites (~70-80% coverage для small-biz target)
- Backend: 5 API endpoints, 3 DB migrations (0007-0009)
- Frontend: /app/imports list + detail + edit + preview
- 95 tests passing
- 13 ADRs (0007-0019) documenting all design decisions
- См docs/url_import_spec.md, docs/runbooks/url_import_deployment.md
- Pending: legal review (lawyer ~$500-1000), production DB migration, deploy
Extension MVP (capture для auth-gated sites) — DONE этой сессии:
- Package: packages/url-import-extension — Chrome/Edge MV3, popup + background SW + content script + overlay state machine + lib (decompose, sanitize, uuidv7, extract-core)
- Auth: temporary manual paste JWT (юзер копирует токен из /app/settings → paste в popup). Web Store + auto-handoff — Phase 15c/15d.
- Destination: existing stagedComponent inbox (Camp 1 persistence — ADR 0019)
- Backend: CORS expanded для chrome-extension://* origins (apps/api/src/index.ts)
- Frontend: settings page +section с token copy widget (apps/web/src/app/app/settings/page.tsx)
- См docs/extractor_mvp_spec.md (canonical), docs/adr/0019-extension-capture-path.md (why)
- Dogfood ready: load unpacked → paste JWT → capture на real auth SPA. Validation cycle открыт.
Live infrastructure
| Service | URL | Notes |
|---|---|---|
| Frontend (CF Pages) | https://arnomake.com (opens in a new tab) (canonical) + https://arno-1hu.pages.dev (opens in a new tab) (CF default) | static export Next.js, auto-deploy on push. arno-ijr.pages.dev устарел и не резолвится |
| Backend (CF Workers Paid) | https://arno-api.vadimpianof.workers.dev (opens in a new tab) (+ custom api.arnomake.com) | Hono + Drizzle Neon HTTP driver. Auto-deploy через .github/workflows/ci.yml step "Deploy backend → Cloudflare Workers (prod)" на push в main + healthcheck. Manual fallback: cd apps/api && pnpm exec wrangler deploy --config wrangler.toml (без --config wrangler 3.x валится). |
| DB (Neon Postgres 17) | ep-dry-block-al36bkvg.c-3.eu-central-1.aws.neon.tech/neondb | Frankfurt, free tier |
| ARNO repo (GitHub) | https://github.com/Arnomake/arno (opens in a new tab) (был vadimpianov/arno — redirect жив) | main → arnomake.com via CI deploy step (CF git auto-integration не работает — см ci.yml) |
| Connected DS repo | https://github.com/vadimpianov/- (opens in a new tab) | renamed а → -. Generic connected DS slot для render bundle. |
| Connected DS content repo | https://github.com/vadimpianov/arno-design (opens in a new tab) | NEW (ADR 0023). main branch = prod target (Arno-dev), staging branch = test target (Arno-test). Deploy button писует pages/{id}.layout.json сюда. |
| Render adapter gh-pages | https://vadimpianov.github.io/-/arno-bundle.js (opens in a new tab) | render bundle, GH Action rebuild on push к - repo |
| Liveblocks dashboard | https://liveblocks.io (opens in a new tab) | prod project (originally stitch, переименован в arno), test project arno-test |
| Test env frontend | https://feat-layout-grid-sl4j.arno-1hu.pages.dev (opens in a new tab) | CF Pages alias of latest feat/layout-grid deploy. NB: bare feat-layout-grid.arno-1hu.pages.dev (no -sl4j) is a stale alias serving an old build — CF appends a hash suffix to long branch names. Use the -sl4j alias or the immutable <hash>.arno-1hu.pages.dev from CI's "Deploy frontend" log. |
| Test env backend | https://arno-api-test.vadimpianof.workers.dev/health (opens in a new tab) | Hono на Workers, отдельный Worker от prod. Auto-deploy через .github/workflows/deploy-test.yml на push в feat/** или fix/** + healthcheck. |
| Test env Neon branch | ep-holy-resonance-al2n98xv-pooler.c-3.eu-central-1.aws.neon.tech/neondb | Separate branch от prod. Migration journal divergent — initial seed was manual SQL before Drizzle journal tracked, so pnpm migrate push against test re-attempts 0007 and fails on existing tables. New test migrations applied directly via node + @neondatabase/serverless (see ADR 0029 "Test env" section). Prod is clean. |
Secrets / credentials map
Wrangler secrets (на CF Workers, arno-api):
DATABASE_URL— Neon connection stringGITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET— для user OAuth (Phase 9)JWT_SECRET— 256-bit hex, HS256 signingLIVEBLOCKS_SECRET_KEY— для room auth (Phase 11)
Local .dev.vars (apps/api/.dev.vars, gitignored) — те же ключи для wrangler dev.
Frontend env vars: нет. Liveblocks public key не используется (authEndpoint flow).
CF Pages dashboard: project arno, env vars: NODE_VERSION=20, PNPM_VERSION=11.1.3.
GitHub OAuth Apps (verified 2026-06-02 — three Apps mirror three env pairs per master spec §III.2.6):
| App name (GitHub UI) | Client ID | Callback URL | Pair |
|---|---|---|---|
| Arno-prod | Ov23liPTOBHJwGiboeoc | https://api.arnomake.com/auth/github/callback | arnomake.com ↔ arno-api (custom domain) |
| Arno-dev | Ov23liiM4f1napOYRepO | https://arno-api-dev.vadimpianof.workers.dev/auth/github/callback | dev.arnomake.com ↔ arno-api-dev |
| Arno-test | (check via app's edit page in GitHub Settings → Developer settings) | https://arno-api-test.vadimpianof.workers.dev/auth/github/callback | test-*.arnomake.com ↔ arno-api-test |
History footnote: до 2026-06-02 в GitHub UI существовал один App, формально названный «Arno-dev», но callback указывал на arno-api (прод). Этот рассинхрон HANDOFF vs реальности привёл к одному инциденту: при попытке настроить настоящий dev-pair я случайно отредактировал callback прод-App'а. Сейчас имена соответствуют ролям: Arno-prod (renamed) хранит прод, новый Arno-dev — настоящий dev. При любой OAuth-операции верифицируй callback URL ДО редактирования. Device Flow остаётся включённым только на Arno-prod (для Phase 13 CLI).
ARNO GitHub App (Phase 13 sync, separate entity!):
- Name:
arno-dev-vadimpianov - App ID:
3821481(corrected 2026-05-29 from ground truth — sync PRs #4/#5/#6 onvadimpianov/-authored byapp/arno-dev-vadimpianov; deployedGITHUB_APP_IDmints через него. Prior value3810588was stale = ID мёртвого pre-rebrand appStitch dev vadimpianov, который до сих пор installed на-, но не используется — кандидат на удаление после verify test-backend/arno-design) - Client ID:
Iv23li8Ime5iRYpMDc4U(⚠️ не сверено после исправления App ID — re-verify на странице app перед использованием) - Installation ID:
134630026(наvadimpianov/-repo) - Webhook URL:
https://arno-api.vadimpianof.workers.dev/webhooks/github - Permissions: Contents R/W, Pull requests W, Webhooks R/W, Workflows R/W, Packages R/W, Metadata R
- Events: push, pull_request
- Private key + webhook secret в Workers secrets (
GITHUB_APP_PRIVATE_KEY,GITHUB_APP_WEBHOOK_SECRET)
Account ownership (SPOF — see master spec §III.2.9)
- GitHub username:
vadimpianov(был<old-github-user>, переименован) - CF account: vadimpianof@gmail.com
- Neon account: GitHub OAuth (vadimpianov)
- Liveblocks: GitHub OAuth (vadimpianov)
- Single-owner risk для всех — Phase 16 launch checklist требует multi-owner CF + secrets backup в 2 password managers.
Architecture decisions (ADRs)
Все в docs/adr/:
- 0001 — Static export Pages сейчас, edge adapter когда Auth/API
- 0002 — Module-level stores +
useSyncExternalStore(composition/library/workflow) - 0003 — Design-system source: GitHub Contents API + raw URLs (не local snapshot)
- 0004 — Render adapter postMessage protocol v1 (iframe sandbox)
- 0005 — Dedicated Worker
arno-api, не Pages Functions - 0006 — Backend-driven OAuth (deviation от master spec §III.2.6) — Auth.js не работает со static export
- 0007-0019 — URL-import / Extension MVP series
- 0020 — Layout Grid engine (recursive Container/Content, 5 zones)
- 0021 — Scale ceiling 30K MAU
- 0022 — Multi-env setup (test + prod isolated)
- 0023 — Deploy pipeline (JSON push в connected repo, не codegen)
- 0024 — Storybook design system (replace handcrafted HTML)
- 0025 — Adaptive overrides + Tokens publish (CSS vars hot-swap)
- 0026 — Navigation perf paradigm (module cache + optimistic render)
- 0027 — Composition zones unification + deploy push_lock mutex
- 0028 — Deploy JSON consumer resolved = defer until demand (3 options + triggers)
- 0029 — Auto-apply prod migrations в CI (solo-dev deviation от §III.2.5, re-eval triggers)
Monorepo layout (стабильный)
apps/
web/ — Next.js 14 App Router (CF Pages, static export)
api/ — Hono + Drizzle (CF Workers Paid)
packages/
shared/ — domain types + pure functions (no React)
editor/ — md-parser + store classes (composition/library)
render-adapter/ — typed postMessage protocol
db/ — Drizzle schema + Neon HTTP driver
tools/
tsconfig/ — base, nextjs, node
eslint-config/ — base, next
migrate/ — Drizzle Pool driver runner (CI/local)
docs/
adr/ — 0001-0006 architecture decisions
development.md, cloudflare-pages.mdCI: .github/workflows/ci.yml (typecheck + lint + drift check + tests + Storybook build + worker bundle gate + Playwright + deploy main).
Test env CI: .github/workflows/deploy-test.yml (на feat/** push — Storybook + web + backend test deploy + healthcheck).
Connected DS bundle: .github/workflows/arno-build.yml в vadimpianov/- (bundle → gh-pages).
Что 🟢 в DB
Schema (11 migrations applied — последняя 0011_low_dorian_gray.sql ADR 0027):
user(Phase 9, GitHub OAuth)project+project.owner_idFKworkflow(nodes/edges JSONB — Postgres mirror, primary в Liveblocks Yjs)screen_composition(instances JSONB legacy +zones JSONB nullablecanonical — ADR 0027 expand-contract; drop instances после 30d soak)project_share_link(Phase 10)component_md_raw+component_md_versions+ triggerpurge_old_md_versions()AFTER INSERT OFFSET 20 (Phase 12)connected_repo+session_branch_state(Phase 13)component_drift(Phase 14)staged_component(URL-import V1, parked)deleted_users(Phase 16 GDPR)
Что задеплоено backend (arno-api)
| Endpoint | Auth | Purpose |
|---|---|---|
GET /health | public | liveness |
GET/POST /auth/github/*, /auth/me, /auth/logout | mixed | OAuth flow + Bearer JWT issue/revoke |
GET /api/v1/me/projects | Bearer | per-user list |
POST /api/v1/projects | Bearer | create |
DELETE /api/v1/projects/:id | Bearer + ownership | delete |
POST /api/v1/projects/:id/share | Bearer + ownership | generate share link |
GET /api/v1/projects/:id/shares | Bearer + ownership | list |
DELETE /api/v1/shares/:id | Bearer + ownership | revoke |
GET /api/v1/public/share/:token | public | viewer snapshot |
GET/PUT /api/v1/workflow/:id | Bearer + ownership | Postgres mirror (primary в Liveblocks) |
GET /api/v1/compositions/:id | Bearer + ownership | list — returns { zones, instances } per screen (ADR 0027 dual-shape) |
PUT /api/v1/compositions/:id/:screenId | Bearer + ownership | accept { zones } (canonical) OR { instances } (legacy) — server dual-writes (ADR 0027) |
POST /api/v1/projects/:id/deploy | Bearer + ownership | ADR 0023 — push zones to connected_repo. v2.0 JSON, KV push_lock mutex (ADR 0027) |
POST /api/v1/liveblocks/auth | Bearer + ownership | room auth, returns session token |
GET/PUT /api/v1/projects/:id/md?path= | Bearer + ownership | MD load + save с 409 conflict |
GET /api/v1/projects/:id/md-versions?path= | Bearer + ownership | last 20 |
GET/POST /api/v1/projects/:id/sync | Bearer + ownership | Phase 13 — push pipeline + status |
POST /webhooks/github | HMAC-verified | GitHub webhook handler (push to main resync) |
GET/POST /debug/github/*, /debug/projects/:id/sync | ENVIRONMENT=development | Phase 13 acceptance helpers (ping, deliveries, redeliver, merge-pr) |
Что в frontend routes
/ — landing (sign-in CTA если no JWT)
/app — projects list (после sign-in)
/app/workflow?project= — workflow editor (Liveblocks room)
/app/screen?project=&id= — composition editor
/app/screen/preview?... — preview iframe (connected DS render bundle)
/app/component?project=&path= — MD editor (Phase 12)
/share?token= — public viewer (read-only canvas)
/share/preview?token=&id= — public prototype walk✅ Завершённые milestone-логи (Phase 13 sync, Phase 14 drift, Layout Grid, Composition zones, Security debt rotation) вынесены в
HANDOFF-archive.md. Здесь остаётся только актуальное состояние + evergreen-справка (infra / secrets / ADR / routes / checklists).
🛠 One-time CI setup checklist
To make the prod auto-deploy pipeline work end-to-end (ADR 0029), set these GitHub Actions repo secrets (Settings → Secrets and variables → Actions):
| Secret | Value source | Why | Status |
|---|---|---|---|
CLOUDFLARE_API_TOKEN | CF dash → API tokens → scoped to Workers:Edit + Pages:Edit | Used by both web Pages deploy and Workers deploy steps | 🟡 verify set |
CLOUDFLARE_ACCOUNT_ID | CF dash → account home → right sidebar | Same | 🟡 verify set |
PROD_DATABASE_URL | Neon prod connection string (ep-dry-block-al36bkvg...) | Used by "Apply DB migrations (prod)" step before backend deploy. Missing this = CI fails fast at the migration step (intentional — better than silent skip) | 🟢 set 2026-05-27 via gh secret set |
CF_API_TOKEN_TEST, CF_ACCOUNT_ID, CF_WORKERS_SUBDOMAIN | Same as prod but scoped to test | Used by deploy-test.yml on feat/** push | 🟡 already set per recap |
Test env intentionally has no auto-migrate step (test Neon journal divergent, see ADR 0029 Test env note). Apply ad-hoc:
DATABASE_URL="postgresql://...test-neon..." \
pnpm --filter @arno/migrate apply-one 20260531050728_perpetual_calypsoapply-one (tools/migrate/src/apply-one.ts) runs a single migration by tag and records it in drizzle.__drizzle_migrations so a future migrate push skips it — bypasses the journal-divergence problem without requiring a full reconciliation.
🛠 Dev env one-time checklist (ADR 0022 v2.1 — 2026-06-02)
Invariant: every env is a frontend↔backend pair, deployed atomically by one workflow. arnomake.com ↔ arno-api (release.yml). dev.arnomake.com ↔ arno-api-dev (ci.yml on main push). test-*.arnomake.com ↔ arno-api-test (deploy-test.yml on feat/**). Web build is identical across envs — host-based switch in api-client.ts:HOST_API decides backend.
Owner-side steps (one-time, ~10 min in UIs):
- Neon — branch
devfromproduction. Console → Project → Branches → Create fromproduction→ copy pooler connection string. Add as GH Actions secret:gh secret set DEV_DATABASE_URL --repo vadimpianov/arno # paste the pooler URL when prompted - Cloudflare KV — dev namespaces. Locally with CF API token authed:
Replace
cd apps/api && pnpm exec wrangler kv namespace create AUTH-DEV pnpm exec wrangler kv namespace create SYNC-DEVREPLACE_WITH_DEV_AUTH_KV_ID/REPLACE_WITH_DEV_SYNC_KV_IDinapps/api/wrangler.dev.tomlwith the returned IDs → commit. - GitHub OAuth App —
Arno-dev. GitHub → Settings → Developer settings → OAuth Apps → New:- Name:
Arno-dev - Homepage:
https://dev.arnomake.com - Callback:
https://arno-api-dev.<cf-subdomain>.workers.dev/auth/github/callback - → save → copy Client ID + new Client Secret.
- Name:
- Liveblocks — new project
arno-dev. Dashboard → New project → namearno-dev→ copy Secret key. - Wrangler secrets on
arno-api-dev(run locally, requires CF API token authed):cd apps/api pnpm exec wrangler secret put DATABASE_URL -c wrangler.dev.toml pnpm exec wrangler secret put JWT_SECRET -c wrangler.dev.toml # openssl rand -hex 32 pnpm exec wrangler secret put GITHUB_CLIENT_ID -c wrangler.dev.toml pnpm exec wrangler secret put GITHUB_CLIENT_SECRET -c wrangler.dev.toml pnpm exec wrangler secret put LIVEBLOCKS_SECRET_KEY -c wrangler.dev.toml # Phase 13 sync — reuse arno-dev-vadimpianov GitHub App (3821481): pnpm exec wrangler secret put GITHUB_APP_ID -c wrangler.dev.toml # 3821481 pnpm exec wrangler secret put GITHUB_APP_PRIVATE_KEY -c wrangler.dev.toml pnpm exec wrangler secret put GITHUB_APP_WEBHOOK_SECRET -c wrangler.dev.toml - GH Actions secret
CF_WORKERS_SUBDOMAIN— already set per test-env recap. Verify:gh secret list --repo vadimpianov/arno | grep CF_WORKERS_SUBDOMAIN - First merge into main →
ci.ymlruns the new dev-pair block. Watch the run; healthcheck step provesarno-api-devis reachable before web flips.
Reuse decisions (long-term):
- GitHub App
arno-dev-vadimpianov(3821481) shared with prod — webhook URL stays on prod, dev does not receive push events (no Phase 13 sync on dev; defer until needed). - DNS
api-dev.arnomake.comdeferred —arno-api-dev.<sub>.workers.devis sufficient (backend is internal per ADR 0022 §5).
⬇️ Phase 15 — Onboarding flow + Extension full UX — NEXT
Goal: Full first-run UX от sign-up до unblocked editing. Подразделяется на 4 фазы (см ADR 0019 + extractor_mvp_spec.md §16):
| Phase | Что | Effort | Что разблокирует |
|---|---|---|---|
| 15a — Auth foundation | Google + Apple + Email magic link backend + multi-provider user upsert + 0010_user_provider.sql + auth tab page | 18-22h | Multi-provider sign-in (target audience ~95% coverage vs ~30% GitHub-only) |
| 15b — Wizard + onboarding | 4-card project chooser + browser detection + onboarding screens + /app/settings extension status badge + auto-create default project | 12-15h | Шаги 1-4 §2 spec'a (signup → wizard → onboarding) |
| 15c — Web Store publish | manifest.key pinned + real branding icons + $5 Google dev account + submission | 1-2h + 1-3 days review | Один-клик install из стора (replaces load-unpacked dogfood) |
| 15d — Auto-handoff | externally_connectable manifest entry + arno.app/auth/extension-flow handshake page + popup multi-provider buttons + replace token copy widget со status badge | 4-6h | Manual paste убирается — финальный UX (Шаг 5 §2 spec'a) |
Total Phase 15: ~50-60h focused work + 1 неделя Google review = 2-3 недели до full UX.
Phase 16 launch checklist key items (для context)
Per master spec §VII.2 — нужно перед public launch:
- All MVP scope §IV implemented (Phase 0-15 cover)
- Test coverage 90% critical paths
- Lighthouse a11y >90, bundle <4MB
- Multi-owner CF account + DNS TTL 300s + secrets в 2 password managers
- All PAGE alert runbooks написаны
- DPAs signed с vendors (CF, Neon, Liveblocks, Sentry, Grafana, Resend)
- GDPR data export/delete endpoints
- ToS + Privacy Policy published
Quick test plan (verify state в новой сессии)
# Prod liveness
curl https://arno-api.vadimpianof.workers.dev/health # → {"ok":true,"ts":...}
curl -I https://arnomake.com/ # → HTTP/2 200
# Test env liveness
curl https://arno-api-test.vadimpianof.workers.dev/health
curl -I https://feat-layout-grid-sl4j.arno-1hu.pages.dev/ # NB: -sl4j, голый alias stale
# Connected DS render bundle
curl -I https://vadimpianov.github.io/-/arno-bundle.js # → HTTP/2 200, ~395KB
# DB schema drift check (no DATABASE_URL hardcode — use shell env / ~/.env)
cd tools/migrate && DATABASE_URL='postgresql://ci:ci@ci.local/ci' \
pnpm exec drizzle-kit generate
# → "No schema changes" если код + migrations синхронныЕсли всё 🟢 — стек целый.
Если что-то сломалось
- CF deploy failed —
gh run list --repo vadimpianov/arno --limit 3 - API 500 —
wrangler tail --config apps/api/wrangler.toml(live logs) - DB unavailable — Neon dashboard https://console.neon.tech (opens in a new tab) (auto-suspend free tier — open проект чтобы wake up)
- Liveblocks broken — dashboard → твой project → connection status
- Bundle outdated — push в connected DS repo (
vadimpianov/-) → GH Action перебилдит за ~1 min
Last updated: session 2026-05-29 — font subsystem (B5) closeout. См полный backlog: docs/BACKLOG.md.
Сделано за сессию 2026-05-29 (Claude session, commits 56c5213..56e8bf2 в каноне feat/layout-grid):
- B5b-2/3 (
56c5213) — BYOF: таблицаproject_font(base64 в Postgres, нет R2) + миграция;apps/api/src/fonts.ts(POST/GET /projects/:id/fonts,GET .../fonts/faces,DELETE /fonts/:id, license обязателен, ~3MB guard); вкладка Fonts в/app/library(drag-drop + license + OpenType auto-maplib/opentype-name.ts, verified 8/8 на системных ttf/otf);@font-facedata-URI embed в reassembly+grid (lib/font-faces.ts). Миграция применена на test-Neon; deploy-test зелёный. - Font Phase A (
1d04c40) — Figma local-first:lib/local-fonts.ts(кроссбраузерный probeFontFace+local()без permission;queryLocalFontsenumerate — Chromium;localFaceCss). Панель: статусы On your computer / Provided for deploy / Missing; upload переименован в «provide for publishing». Reassembly+grid рендерят установленные шрифты черезlocal(). ADR 0030 §6 обновлён (render: uploaded→local()→Google→system). - A2 replace-picker (
a496ed9) —captured_page.font_overridesjsonb (миграция на test-Neon) +PATCH /captured-pages/:id. Missing → дропдаун Replace (free ∪ локальные) + Clear, статус Replaced · → X; рендер переписываетfont-familyв токенах И в leaf-html (overrideStack/overrideHtmlFonts). - Deploy-gate (B) — ОТЛОЖЕН (
8069fd0, ADR 0030 §6). Аудит: публичной редистрибуции шрифтов нет (embed только в редакторе у владельца; share-viewer = только workflow-канвас; deploy = layout-JSON без рендерера, ADR 0028). Re-open триггер: публично-рендерящийся артефакт начнёт встраивать шрифты. - Фиксы:
cdb69f6попап расширения показывает активный env-хост (не захардкоженный arnomake.com);3619abfпанель Fonts защитно реклассифицирует устаревшие captures (Inter/JetBrains → google, прячет*Fallback);46b14dfenv-aware JWT TTL (тест 90д / prod 7д) — manual-paste токен на тесте перестаёт протухать каждые 7д + поправлен устаревшийFRONTEND_ORIGIN→sl4j + копи попапа;fe7c957deploy-кнопка вынесена на уровень workflow-страницы (была вложена в graph-fallbackReconstructedPage→ у capture-проектов с reassembly пропадала). - CLAUDE.md — правило:
AskUserQuestionне работает в Claude Desktop agent mode (SDK-сабпроцесс без TTY, пустой ответ — known issue), спрашивать текстом. - 🚀 MERGE feat/layout-grid → main (PROD) по явной директиве юзера — merge commit
8007c13(чистый 3-way, 0 конфликтов; на main до этого было 24chore(brand)-коммита, только вbrand/, миграций не было). Пуш вmainзапустилci.yml: typecheck/lint/tests/drift/Playwright → прод-миграции (ADR 0029, аддитивные) → деплой backendarno-api→ деплой webarnomake.com. Прод-Neon мигрируется автоматически (project_font, font_overrides, zones, entry_mode, captured_page). После этого канон (feat/layout-grid) и main сходятся. - Pending (юзер): browser-verify B5b-2/3+A+A2 на стенде
feat-layout-grid-sl4j.arno-1hu.pages.dev(sign-in → Fonts → установленный кастом рендерится; Missing → Replace работает). Гард-правило «decision-of-record (ADR/scope/архитектура) — показать выбор и ждать явного да перед фиксацией; авто-commit только для обратимого кода» — добавлено вCLAUDE.md(2026-05-29).
Сделано за сессию 2026-05-28 (Claude session, commits 2c60708..0978c7d):
- ADR 0027 — composition zones unification + deploy push_lock mutex. Migration 0011 (
screen_composition.zones) applied на test, backfill 3/3 rows. Backend dual-shape (zones canonical + instances legacy soak 30d). Deploy serializes full zones (v2.0 JSON). User-verified UI: sidebar persists, deploy JSON has zones. - ADR 0028 — JSON deploy consumer resolved = defer until demand (not pending work).
- ADR 0029 — auto-apply prod migrations в CI (solo-dev deviation от §III.2.5).
PROD_DATABASE_URLsecret set. Manual approval gate removed. - CI — prod backend deploy automated on push to main (was manual
wrangler deploy). - Perf — subscription leak fix (cross-project corruption) + Liveblocks defer (−65KB First Load на editor/workflow) + dead bootSnapshot cleanup.
- Phase 14 — verified end-to-end (correction: было «not verified», но drift workflow ran 2026-05-23, component_drift populated).
- Runbook —
docs/runbooks/composition_zones_backfill.md(prod backfill procedure для soak-end). - BACKLOG.md — consolidated not-done list created.
Параллельная сессия (same worktree) сделала Phase 15b part A — entry_mode capture flow (bb5eb1e, 70c0ac5). Migration 0012 (project.entry_mode) applied на test. Histories reconciled, git clean.
Pending (требует юзера):
- 🔴 6 секретов rotation (через chat прошли)
- Merge feat/layout-grid → main (prod migration авто через CI после merge)
- Master spec — auto-reconciles, no manual work. feat
_index.md= v1.4, main = v1.5 (scale ceiling), но feat не трогала этот файл после ветвления (git log merge-base..feat -- _index.mdпусто) → git 3-way merge возьмёт main's v1.5 автоматически, без конфликта. Changelog bump для ADR 0027-0029 сделать на main после merge (одна строка). Merge origin/main → feat ВЫПОЛНЕН (2026-05-28): разрешены конфликты — migration collision (main0011_faithful_slapstickstaged_component.project_id + feat zones/entry_mode → перегенерированы в чистую линейную0012_free_amphibian), CORS (взят main's unit-tested./corsмодуль), projects-list (feat's refactored entry_mode modal), ADR index (regenerated; удалён дубликат0021-scale-ceiling-30k-mau.mdв пользу main's0021-scale-target.md). feat теперь содержит main's историю → финальный feat→main merge будет чистый.
Parallel on main (2026-05-28): Extension v1 + v2 layout mode полностью реализованы (v1 тег v1.0-components). Filter chips, Escape modal, ghost filter, thumbnail fix, pseudo-element bg fix — документированы + проверены. AutoTests manual checklist в packages/url-import-extension/AutoTests/manual.md. (Пришло в feat через merge.)
Earlier 2026-05-27: Layout Grid Phase 1-3 DONE + 4-commit perf series (e0a20ba..c544df2) на feat/layout-grid. ADR 0020-0026 written. Test env live на feat-layout-grid.arno-1hu.pages.dev.
Previous (2026-05-24): Extension MVP DONE (7 commits, packages/url-import-extension live). Capture pipeline на dogfood-ready load-unpacked install. URL-import V1 (CLI) still architecturally complete + parked. Phase 14 backend + frontend committed (26cba92); end-to-end drift verified (см Phase 14 section — workflow ran 2026-05-23, component_drift populated). Phase 15 reframed на 4 sub-phases.