Conloca is a visual CMS for Astro sites: editors change pages in the browser and the content is committed to Git. In October Ultra / Mextner delivered fixes across the editor, invites and sign-in, content and media handling, and the production site.
The documentation theme picker is implemented and checked but awaits team review, so it is shown separately below.
397.6 → 0 px
How it works image movement during delayed hydration, in the browser test of #342
33.6 MB
snapshot that now imports in bounded parts of at most 16 MiB (#259)
409
answer to a write from an editor tab whose repository, branch or site differs, given before any billing, storage or Git work (#192, #224)
What we delivered
Editor and page actions
Dragging blocks into a page, Undo after adding a block and the Dashboard New Page button now work. A disabled Preview button explains why it is unavailable. Never-published pages read Draft in the Pages list and on the Dashboard. A route or schema error shows a CMS error page or notice instead of the developer screen, and the branch-name field formats as you type. These were everyday actions that did nothing or showed the wrong state.
Technical detail
Puck ignores a press on a real <button>, so the block-list item is a div role="button" again, and an empty page says how to add a block (#299). Undo and Redo remove and restore a block added by drag or double-click (#306).
The Dashboard New Page button opens the create-page dialog in place (#302). One pageStatusOf() and a LocaleVersion.unpublished marker decide page status, and saving a new translation keeps a never-published page a draft (#296, #307).
A route that fails to render shows RouteErrorPage, and a site schema that is not a generated host schema shows a notice instead of crashing the CMS (#301, #293, #295). The disabled Preview button shows its explanation when clicked (#262).
The New branch field lowercases, hyphenates and keeps the caret in place as you type (#297, #316). The editor opens pages with no English variant (#196), and draft previews parse records with each collection’s schema so dates and defaults match the build (#248).
Frontmatter keys such as __proto__.polluted cannot mutate Object.prototype, while own-data keys, including legitimate constructor and prototype names, are kept (#239). The sign-in divider is readable in dark mode; login-theme.spec.ts checks light and dark at 390 and 1280 px (#220).
Accepting an invite lands in the invited workspace, from both the in-app button and the emailed link. Each editor tab sends its repository, branch and site with every write, and the server rejects a mismatch with a 409 instead of writing to the wrong place. Tabs of one browser origin that refresh at the same moment share one token rotation instead of revoking the whole token family.
Technical detail
Mutations carry X-Conloca-Editor-Repo, -Branch and -Site headers. The worker answers 409 workspace-mismatch before any billing, storage or Git work, and the tab re-enters its own branch and retries once (#192, #224).
Accept waits at most 5 seconds for the membership flush and the Org Durable Object’s alarm delivers the rest; an invite screen that would stay on “accepting” is released after 20 seconds (#292, #305). A deployed-stage proof, invite-landing.spec.ts, covers an invitee who already has a workspace (#294).
A second use of a refresh cookie within 10 seconds gets the live token from the rotation chain, and the browser refreshes under a Web Lock so same-origin tabs send one request per rotation (#240). An OAuth failure shows its failure screen instead of looping (#225).
Splitting the sign-in and Members screens into props-only views fixed a double submit that requested two sign-in links and a double click that sent two invitations (#253, #223).
Cloudflare Access configuration fails closed with 401 when only one of its two settings is present (#191). Overlay rows for pages deleted in Git are pruned (#188). Workspace and flag types are narrowed instead of cast (#222).
Scoped content commits, safe asset moves and correct write-back
CMS commits include only content files, so other people’s staged work is left out, and an asset move claims its destination instead of overwriting an existing file. Draft images render in the canvas before Publish, and a docs-root page is written back to the file it was read from. The hosted preview shows pages committed after its build, and a site snapshot over Cloudflare’s 32 MiB call limit imports in bounded parts.
Technical detail
CMS commits build their tree in a private index seeded from HEAD and move HEAD by compare-and-swap with update-ref, so a concurrent commit is not reverted. Asset moves run one at a time and claim the destination with a hard link. Tests use real temporary repositories: foreign staged files, two concurrent instances, a held index.lock and concurrent moves racing for one name (#197).
Draft images are served from /api/v1/assets/draft/<folder>/<file> while stored data keeps the canonical /assets/... reference (#243). A docs-root MDX page used to be written to a shadow file; mdx-pages-root.test.ts covers edit, delete, create-after-delete and a collision (#235).
The hosted preview records its own buildCommit and sends it as ?build=; the worker diffs that commit against the branch head and matches entries by blob id. A deployed-stage test commits a title without redeploying and expects the new heading (#247, spec change #228).
The depth-1 snapshot of one pull request packs to about 33.6 MB, over the 32 MiB RPC limit. It now lands in parts of at most 16 MiB with the tree and commit last, and a live check against github.com imported the full closure of 4,836 objects (#259). A missing base tree is fetched by object id instead of failing the import (#256).
Org import now always retries the tracker reconcile when a ref already matches upstream, so a stale preview session recovers (#177). Only three of Ultra’s 29 commits in #177 are credited: the repair, its at-head regression test and the browser publication proof. The vocabulary sample stays off the Git lookup behind the unpublished marker (#308).
The production How it works page uses the completed design: a four-step flow with MDX, JSON and editor visuals, twelve responsive images and an install banner that survives 200% text. The design is by Saba; our work is the integration into the production site and its checks. In the pull request’s delayed-hydration test, images moved 397.6 px before the fix and 0 px after it.
Technical detail
The twelve PNGs were imported byte for byte, with SHA-256 and byte lengths checked on the preview. Existing routes, app links, docs links and install tabs are kept, and text is darkened where the original failed contrast (#342).
The test measured 397.587 px of movement before and 0 px across 53 frames after. Twenty-one website targets and 50 browser checks were rerun.
The merge to the main branch ran Validate, deployed-stage browser tests and Deploy Production, all successful. Locally, the split-port development stack redirects backend landings to the app and keeps published media on local origins (#231).
The CMS and the app each build a static Storybook catalogue in CI, and state stories cover save, sync and workspace status, the page list and the conflict dialogs. Deployed-stage browser tests walk every CMS route, create a branch through the Branch menu, confirm a new page reads Draft and check that typed text survives reopening the editor in both themes. CI keeps the proof folder of every deployed-stage run.
Technical detail
State stories render the real components with fixtures that stop at HostClient and the content API fetch, so nothing reaches the network. Counts: SaveSlot 6 plus phone, SyncStatusSlot 8 plus phone, WorkspaceStatusSlot 8 plus phone, CmsHostPlaceholder 9 (#244). CI saves a screenshot of every story at 1280 × 800 and 390 × 844. The app Storybook runner now compiles Tailwind, because classes such as max-sm:hidden were missing from all app examples.
A flaky failure in the Several Locales scenario, caused by a viewport resize landing mid-play, is gone: resizes during play went from 30 and 11 to 0 and 0 in the probe runs (#257).
The deployed-stage job keeps its proof folder on every run and the Playwright report on failed runs (#320). A manual staging proof workflow adds two new legs (#261), and the emitted billing deploy bundle runs in CI (#238).
Real-engine and typed tests for Git paths, billing identity mapping, workspace recovery and memory behaviour were added or made deterministic (#198, #234, #221, #236, #201, #254). Branch creation and the Draft check are in #229 and #309, and the editor-reopen check is in #226.
A pull-request stage environment that keeps working
Pull-request stages are cleaned up on a schedule with capacity reporting and stage leases, which brought the Cloudflare namespace count to 386 of 500 after the first eviction run. Each stage uses one Durable Object namespace fewer, and each deployment attempt gets its own preview fixture without resetting drafts. Before this, Deploy Stage was failing account-wide with Cloudflare error 10067 after about 170 namespaces leaked from closed pull requests.
Technical detail
A scheduled workflow runs every six hours, reports capacity and fails above 400 of 500 namespaces. Drafts hold a stage for 24 hours and ready pull requests for 72 hours; keep-stage and evict-stage override that. tooling/pr-stage-gc.test.ts checks that only closed same-repo pull requests are selected (#200).
Deleting the unused EventLogDO takes each stage from 10 namespaces to 9, which the pull request estimates moves live capacity from about 48 to about 53 stages (#241).
Readiness rejects dirty overlays and divergent session heads, and older attempt orgs are retired after 24 hours (#169). A read-only inventory of legacy preview resources deletes nothing (#155). A Deploy Production failure on a missing generated contracts module was fixed (#258), and E2E runs leave branches clean (#193).
Project validation moved from hand-written Zod schemas to validators generated from the TypeScript types with Typia. The editor builds its forms from the generated schema metadata, and the loader reports field paths. We adapted the existing generated-contract work to the editor, loader, CLI and sites.
Technical detail
Site schemas become HostSchema<T>: a generated validator with JSON Schema metadata. The editor builds its forms from that metadata, and the loader reports field paths. content-api drops zod and conformance rule A76 forbids it in every workspace package (#251).
Key pull request: #251 · 28 pull requests in this group, most of them build, test and CI changes, all in the source record
Before and after in numbers
Each row compares the state before the change with the state after it. Figures are taken from the pull request named in the last column.
Measure
Before
After
Source
Image movement on How it works during delayed hydration (browser test)
Opened in November and separate from the October merges above. Implemented and checked, not merged.
IMPLEMENTED · NOT MERGED
Documentation theme picker
The documentation site’s native Auto dropdown is replaced by Light, Dark and System controls with a visible selection, keyboard navigation and touch targets. The head of the pull request passed its deployed-stage browser tests, and the preview site is live. The change awaits team review.
The theme control on production and on the PR #489 preview, both captured at 1440 px on 5 November. On a phone the pictures show the header where the control sits; tap one to open the full capture.
Before: native dropdown. Production, 5 November.After: Light, Dark and System choices. PR #489 preview, 5 November.
Proof
Hosted CI runs for the final commit of each pull request, or for the merge commit where noted.
Work by Ultra / Mextner (GitHub alex-mextner, Alex Ultra). Pull requests merged 1 to 31 October 2026 are credited by commit authorship, and 92 merged pull requests are counted, one of them for named commits only. Status checked 6 November. Source links may require repository access.
Attribution: every commit in these pull requests is by Ultra, except #177. #167, #168 and #169 were opened from the smoothbricker account and merged by alex-mextner. #177 is credited only for three of Ultra’s 29 commits there: the stale preview-session repair, its at-head regression test and the browser publication proof. Pull requests whose commits are by other authors are counted only in aggregate in the source record.
All merged pull requests counted here are listed with their authors and merge commits in the source record.
+
Conloca: October 2026
Editor fixes, workspace safety, content integrity, the production How it works page
Conloca is a visual CMS for Astro sites: editors change pages in the browser and the content is committed to Git. In October Ultra / Mextner delivered fixes across the editor, invites and sign-in, content and media handling, and the production site.
The documentation theme picker is implemented and checked but awaits team review, so it is shown separately below.
397.6 → 0 px
How it works image movement during delayed hydration, in the browser test of #342
33.6 MB
snapshot that now imports in bounded parts of at most 16 MiB (#259)
409
answer to a write from an editor tab whose repository, branch or site differs, given before any billing, storage or Git work (#192, #224)
What we delivered
Editor and page actions
Dragging blocks into a page, Undo after adding a block and the Dashboard New Page button now work. A disabled Preview button explains why it is unavailable. Never-published pages read Draft in the Pages list and on the Dashboard. A route or schema error shows a CMS error page or notice instead of the developer screen, and the branch-name field formats as you type. These were everyday actions that did nothing or showed the wrong state.
Owner request (excerpt)
Original request (RU)
все баги которые никто не делает и почини по tdd в параллельных ворктри и влей через gh ship Важны максимально реалистичные е2е тесты В задачи поставь меня исполнителем Приложи пруфами скриншоты до и после
English translation
all the bugs nobody is working on, and fix them with TDD in parallel worktrees and merge through gh ship. Maximally realistic e2e tests are important. Put me as the assignee in the tasks. Attach screenshots from before and after as proof.
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 10:39 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 1ea5051957cda4839152034111af4c51789e7761deb61386ab53bc2627c82c4d.
Inferred link. The instruction under which the October editor bugs were taken from the team's bug database. It names no single pull request.
The opening words of the message and the link to the private bug database are left out.
Owner request (excerpt)
Original request (RU)
И параллельно в новом вт почини
English translation
And in parallel, in a new worktree, fix [the linked bug-database entry “Dashboard New page button bug”].
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 18:09 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 7669ba062bc44797708bedd3cc4b6cbc332f79d389145bf379003cdf2ee20979.
Inferred link. By the title in the link's address (the Dashboard New Page fix, #302). The link itself is not republished.
Owner request (excerpt)
Original request (RU)
еще задача: Cannot drag blocks onto an empty page. no block is added and the canvas remains empty. No error messages or guidance is displayed.
English translation
one more task: [English bug text, as pasted from the bug database:] Cannot drag blocks onto an empty page. no block is added and the canvas remains empty. No error messages or guidance is displayed.
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 23:45 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 e3869c29e076499fd4a50480adf8214b79b44f05f6143248f2f1db6ae57f3d9f.
Inferred link. By topic (the empty-page drag fix, #299). Only «еще задача:» is the owner's wording; the English after it is bug text pasted from the bug database.
Owner request (excerpt)
Original request (RU)
и еще одна: Pages - Status badges don’t reflect real status. All pages are always showing published. Even when I just created one.
English translation
and one more: [English bug text, as pasted from the bug database:] Pages - Status badges don’t reflect real status. All pages are always showing published. Even when I just created one.
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 23:46 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 f2437f51dc49ba9358e730e76d4e740c178b5d078d72d5653d554744d4a7d06c.
Inferred link. By topic (the Draft status fix, #296 and #307). Only «и еще одна:» is the owner's wording; the English after it is pasted bug text.
New branch field: original request and production recording
Owner request (excerpt)
Original request (RU)
и вот эту возьми: New branch - Branch name formatting problem. Problem:
SPACE can’t be used in a branch name, but we still allow users to type it and then show error.
Solution:
We need to automatically format the user inputed text like following: enforce lowercase, automatically type - when user uses SPACE.
English translation
and take this one too: [English bug text, as pasted from the bug database:] New branch - Branch name formatting problem. Problem: SPACE can’t be used in a branch name, but we still allow users to type it and then show error. Solution: We need to automatically format the user inputed text like following: enforce lowercase, automatically type - when user uses SPACE.
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 23:51 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 fdf731854e8a0dd58796a4e33ebfdc825f582d5f60d945a7fed1d64f4b204e3a.
Inferred link. By topic (the branch-name formatting fix, #297 and #316). Only «и вот эту возьми:» is the owner's wording; the English after it is pasted bug text. A screenshot attached to the message was not recovered.
The recording is footage of the production CMS made by the team and attached to pull request #316. This page shows a cropped copy of the New branch form. It checks only that the caret stays in place while the text is formatted.
Recorded production browser checkNew branch field keeps the caret while formatting. Footage recorded by the team on the production site and attached to the pull request. It is not a CI or end-to-end test run and not full acceptance. MP4 file
Page
the New branch form in the production CMS
Pull request
#316 (merged), the original recording shows a signed-in workspace and stays in the private repository; this page shows a cropped copy of the New branch form only
Recorded
29 October 2026, 15:00 UTC (attached to the pull request on 3 November), 10 seconds; real Chrome against the production CMS, signed in; the form was exercised without submitting
What it shows
Typing in the Branch name field while the caret sits after a Greek sigma. The pull request reports five DOM-backed checks passing for this scenario. It is not branch creation or release sign-off.
File
600×460, 10.05 s, 100 frames, no audio. MP4 SHA-256 175ed0005255fa6b785d89339335211cda5760fd74ba0f45ecd38630a0455a8d, WebM alternate SHA-256 8f160203355c2076ac9eedc6b893fc30effaf35460d3a51e2aa58565ccffae71. This is a derived copy: the original recording is not republished; it was cropped to the form (region 300×230 px at 182,40, scaled ×2). Original SHA-256 ce25011c07367aff2de27d53927b8212f55af2815cf55b72c731146268f84c86.
Sign-in divider in the dark theme
Merged, pull request #220 Single capture: after only.
Frame of the recording Frame at 3.9 s of the production recording below: the sign-in divider in the dark theme. Full-size original (1280×800 PNG)
The divider lines and the word “or” are visible against the dark card.
A frame of the production recording of pull request #220 shown below, at 3.9 seconds, 1280×800. The recording has no before capture.
Sign-in divider: production browser checks, desktop and phone width
Footage of the production sign-in page made by the team and attached to pull request #220. Each clip starts in the light theme and switches to the dark theme.
Recorded production browser checkSign-in divider, desktop, light then dark. Footage recorded by the team on the production site and attached to the pull request. It is not a CI or end-to-end test run and not full acceptance. MP4 file
Page
the production sign-in page
Pull request
#220 (merged), attached to the pull request in the private repository conloca/private (access required)
Recorded
3 November 2026 (comment date), 4.1 seconds; real Chrome against production, 1280×800, no account signed in
What it shows
The divider colour in the light and then the dark theme. It is not an accessibility audit and not a pixel baseline.
Recorded production browser checkSign-in divider, phone width, light then dark. Footage recorded by the team on the production site and attached to the pull request. It is not a CI or end-to-end test run and not full acceptance. MP4 file
Page
the production sign-in page
Pull request
#220 (merged), attached to the pull request in the private repository conloca/private (access required)
Recorded
3 November 2026 (comment date), 4.1 seconds; real Chrome against production, 390×844, no account signed in
What it shows
The divider colour in the light and then the dark theme at phone width.
Puck ignores a press on a real <button>, so the block-list item is a div role="button" again, and an empty page says how to add a block (#299). Undo and Redo remove and restore a block added by drag or double-click (#306).
The Dashboard New Page button opens the create-page dialog in place (#302). One pageStatusOf() and a LocaleVersion.unpublished marker decide page status, and saving a new translation keeps a never-published page a draft (#296, #307).
A route that fails to render shows RouteErrorPage, and a site schema that is not a generated host schema shows a notice instead of crashing the CMS (#301, #293, #295). The disabled Preview button shows its explanation when clicked (#262).
The New branch field lowercases, hyphenates and keeps the caret in place as you type (#297, #316). The editor opens pages with no English variant (#196), and draft previews parse records with each collection’s schema so dates and defaults match the build (#248).
Frontmatter keys such as __proto__.polluted cannot mutate Object.prototype, while own-data keys, including legitimate constructor and prototype names, are kept (#239). The sign-in divider is readable in dark mode; login-theme.spec.ts checks light and dark at 390 and 1280 px (#220).
Accepting an invite lands in the invited workspace, from both the in-app button and the emailed link. Each editor tab sends its repository, branch and site with every write, and the server rejects a mismatch with a 409 instead of writing to the wrong place. Tabs of one browser origin that refresh at the same moment share one token rotation instead of revoking the whole token family.
Owner request
Original request (RU)
Задача: When inviting someone to a workspace, they don't always get redirected to the invited workspace when clicking accept invite in a received mail
English translation
Task: [English bug text, as pasted from the bug database:] When inviting someone to a workspace, they don't always get redirected to the invited workspace when clicking accept invite in a received mail
Source. Ultra / Mextner · Ultra's omp prompt history, project conloca · no message id · 22 October 2026, 13:27 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 59a3431d16bdddc2b358f19a80548dfcf20b9e974aeed17282ce6a3d534902aa.
Inferred link. By topic (the invite-landing fixes, #288, #289 and #305). Only «Задача:» is the owner's wording; the English after it is pasted bug text.
Technical detail
Mutations carry X-Conloca-Editor-Repo, -Branch and -Site headers. The worker answers 409 workspace-mismatch before any billing, storage or Git work, and the tab re-enters its own branch and retries once (#192, #224).
Accept waits at most 5 seconds for the membership flush and the Org Durable Object’s alarm delivers the rest; an invite screen that would stay on “accepting” is released after 20 seconds (#292, #305). A deployed-stage proof, invite-landing.spec.ts, covers an invitee who already has a workspace (#294).
A second use of a refresh cookie within 10 seconds gets the live token from the rotation chain, and the browser refreshes under a Web Lock so same-origin tabs send one request per rotation (#240). An OAuth failure shows its failure screen instead of looping (#225).
Splitting the sign-in and Members screens into props-only views fixed a double submit that requested two sign-in links and a double click that sent two invitations (#253, #223).
Cloudflare Access configuration fails closed with 401 when only one of its two settings is present (#191). Overlay rows for pages deleted in Git are pruned (#188). Workspace and flag types are narrowed instead of cast (#222).
Scoped content commits, safe asset moves and correct write-back
CMS commits include only content files, so other people’s staged work is left out, and an asset move claims its destination instead of overwriting an existing file. Draft images render in the canvas before Publish, and a docs-root page is written back to the file it was read from. The hosted preview shows pages committed after its build, and a site snapshot over Cloudflare’s 32 MiB call limit imports in bounded parts.
Technical detail
CMS commits build their tree in a private index seeded from HEAD and move HEAD by compare-and-swap with update-ref, so a concurrent commit is not reverted. Asset moves run one at a time and claim the destination with a hard link. Tests use real temporary repositories: foreign staged files, two concurrent instances, a held index.lock and concurrent moves racing for one name (#197).
Draft images are served from /api/v1/assets/draft/<folder>/<file> while stored data keeps the canonical /assets/... reference (#243). A docs-root MDX page used to be written to a shadow file; mdx-pages-root.test.ts covers edit, delete, create-after-delete and a collision (#235).
The hosted preview records its own buildCommit and sends it as ?build=; the worker diffs that commit against the branch head and matches entries by blob id. A deployed-stage test commits a title without redeploying and expects the new heading (#247, spec change #228).
The depth-1 snapshot of one pull request packs to about 33.6 MB, over the 32 MiB RPC limit. It now lands in parts of at most 16 MiB with the tree and commit last, and a live check against github.com imported the full closure of 4,836 objects (#259). A missing base tree is fetched by object id instead of failing the import (#256).
Org import now always retries the tracker reconcile when a ref already matches upstream, so a stale preview session recovers (#177). Only three of Ultra’s 29 commits in #177 are credited: the repair, its at-head regression test and the browser publication proof. The vocabulary sample stays off the Git lookup behind the unpublished marker (#308).
The production How it works page uses the completed design: a four-step flow with MDX, JSON and editor visuals, twelve responsive images and an install banner that survives 200% text. The design is by Saba; our work is the integration into the production site and its checks. In the pull request’s delayed-hydration test, images moved 397.6 px before the fix and 0 px after it.
How it works on production: browser check
Footage of the production page made by the team and attached to pull request #342. The page is public. There is no before footage.
Recorded production browser checkHow it works page on production. Footage recorded by the team on the production site and attached to the pull request. It is not a CI or end-to-end test run and not full acceptance. MP4 file
Page
https://conloca.com/how-it-works
Pull request
#342 (merged 27 October 2026), attached to the pull request in the private repository conloca/private (access required)
Recorded
28 October 2026, 20:14 to 20:15 UTC; real Chrome against production, viewport 1348×936, no cuts or speed changes
What it shows
Hero, the four-step flow, the layered visual, the cards, the install banner, the pnpm, npm and bun tab selection, and the footer Docs link, on desktop only.
The twelve PNGs were imported byte for byte, with SHA-256 and byte lengths checked on the preview. Existing routes, app links, docs links and install tabs are kept, and text is darkened where the original failed contrast (#342).
The test measured 397.587 px of movement before and 0 px across 53 frames after. Twenty-one website targets and 50 browser checks were rerun.
The merge to the main branch ran Validate, deployed-stage browser tests and Deploy Production, all successful. Locally, the split-port development stack redirects backend landings to the app and keeps published media on local origins (#231).
The CMS and the app each build a static Storybook catalogue in CI, and state stories cover save, sync and workspace status, the page list and the conflict dialogs. Deployed-stage browser tests walk every CMS route, create a branch through the Branch menu, confirm a new page reads Draft and check that typed text survives reopening the editor in both themes. CI keeps the proof folder of every deployed-stage run.
Technical detail
State stories render the real components with fixtures that stop at HostClient and the content API fetch, so nothing reaches the network. Counts: SaveSlot 6 plus phone, SyncStatusSlot 8 plus phone, WorkspaceStatusSlot 8 plus phone, CmsHostPlaceholder 9 (#244). CI saves a screenshot of every story at 1280 × 800 and 390 × 844. The app Storybook runner now compiles Tailwind, because classes such as max-sm:hidden were missing from all app examples.
A flaky failure in the Several Locales scenario, caused by a viewport resize landing mid-play, is gone: resizes during play went from 30 and 11 to 0 and 0 in the probe runs (#257).
The deployed-stage job keeps its proof folder on every run and the Playwright report on failed runs (#320). A manual staging proof workflow adds two new legs (#261), and the emitted billing deploy bundle runs in CI (#238).
Real-engine and typed tests for Git paths, billing identity mapping, workspace recovery and memory behaviour were added or made deterministic (#198, #234, #221, #236, #201, #254). Branch creation and the Draft check are in #229 and #309, and the editor-reopen check is in #226.
A pull-request stage environment that keeps working
Pull-request stages are cleaned up on a schedule with capacity reporting and stage leases, which brought the Cloudflare namespace count to 386 of 500 after the first eviction run. Each stage uses one Durable Object namespace fewer, and each deployment attempt gets its own preview fixture without resetting drafts. Before this, Deploy Stage was failing account-wide with Cloudflare error 10067 after about 170 namespaces leaked from closed pull requests.
Technical detail
A scheduled workflow runs every six hours, reports capacity and fails above 400 of 500 namespaces. Drafts hold a stage for 24 hours and ready pull requests for 72 hours; keep-stage and evict-stage override that. tooling/pr-stage-gc.test.ts checks that only closed same-repo pull requests are selected (#200).
Deleting the unused EventLogDO takes each stage from 10 namespaces to 9, which the pull request estimates moves live capacity from about 48 to about 53 stages (#241).
Readiness rejects dirty overlays and divergent session heads, and older attempt orgs are retired after 24 hours (#169). A read-only inventory of legacy preview resources deletes nothing (#155). A Deploy Production failure on a missing generated contracts module was fixed (#258), and E2E runs leave branches clean (#193).
Project validation moved from hand-written Zod schemas to validators generated from the TypeScript types with Typia. The editor builds its forms from the generated schema metadata, and the loader reports field paths. We adapted the existing generated-contract work to the editor, loader, CLI and sites.
Technical detail
Site schemas become HostSchema<T>: a generated validator with JSON Schema metadata. The editor builds its forms from that metadata, and the loader reports field paths. content-api drops zod and conformance rule A76 forbids it in every workspace package (#251).
Key pull request: #251 · 28 pull requests in this group, most of them build, test and CI changes, all in the source record
Before and after in numbers
Each row compares the state before the change with the state after it. Figures are taken from the pull request named in the last column.
Measure
Before
After
Source
Image movement on How it works during delayed hydration (browser test)
Opened in November and separate from the October merges above. Implemented and checked, not merged.
IMPLEMENTED · NOT MERGED
Documentation theme picker
The documentation site’s native Auto dropdown is replaced by Light, Dark and System controls with a visible selection, keyboard navigation and touch targets. The head of the pull request passed its deployed-stage browser tests, and the preview site is live. The change awaits team review.
The theme control on production and on the pull request #489 preview, both captured at 1440 px on 5 November. Each crop cuts the header where the control sits. Click a crop to open the full capture. The before image is production and the after image is the preview of an open pull request that is not merged.
Documentation theme control
Open pull request, not merged Before and after: same window, same region, same scale.
Before · production Production conloca.com, captured 5 November 2026: the theme control is a native select. Full-size original (1440×1000 PNG)
It is a native select that opens an operating-system menu.
After · PR preview Pull request #489 preview stage, same page, viewport and crop: Light, Dark and System as a visible control. Full-size original (1440×1000 PNG)
Three visible choices; the active one, System, is highlighted.
Same page (getting-started), viewport 1440×1000 and crop, captured two seconds apart on 5 November 2026. The before image is production conloca.com. The after image is the pull request #489 preview stage, a different deployment of an open pull request.
Proof
Recordings. The four clips on this page are production browser checks recorded by the team and attached to pull requests. They are not CI or end-to-end test runs and not full acceptance. The repository’s CI keeps no video or trace artifact, and a CI job page is not a recording, so none is presented as one.
Owner priority (excerpt)
Original request (RU)
Полностью рабочая конлока в нашем продукте, подтвержденная е2е тестами и скриншотами
English translation
A fully working Conloca in our product, confirmed by e2e tests and screenshots.
Source. Ultra / Mextner · Telegram, private chat between Ultra and the assistant bot · Telegram message id 180834 · 18 August 2026, 17:54 UTC. Sender: incoming Telegram message from the owner's account (not the assistant, not another person). Full-message SHA-256 d65be6db2be49447aa7b6467294c830b9bbc2c0007f3f48de41a7a4d7ccf4618.
Inferred link. Context for the proof below, not for one outcome: the owner's standing priority for Conloca as stated in a message about the day's work.
18 August 2026, before the October period of this report; kept because it states the owner's standing priority.
Hosted CI runs for the final commit of each pull request, or for the merge commit where noted.
Work by Ultra / Mextner (GitHub alex-mextner, Alex Ultra). Pull requests merged 1 to 31 October 2026 are credited by commit authorship, and 92 merged pull requests are counted, one of them for named commits only. Status checked 6 November. Source links may require repository access.
Attribution: every commit in these pull requests is by Ultra, except #177. #167, #168 and #169 were opened from the smoothbricker account and merged by alex-mextner. #177 is credited only for three of Ultra’s 29 commits there: the stale preview-session repair, its at-head regression test and the browser publication proof. Pull requests whose commits are by other authors are counted only in aggregate in the source record.
All merged pull requests counted here are listed with their authors and merge commits in the source record.
diff --git a/reports/hyper-2026-10/media-evidence.json b/reports/hyper-2026-10/media-evidence.json
new file mode 100644
index 0000000..b97ce89
--- /dev/null
+++ b/reports/hyper-2026-10/media-evidence.json
@@ -0,0 +1,2129 @@
+{
+ "report": "hyper",
+ "generatedAt": "2026-11-06",
+ "note": "Public-safe subset: hashes, crop coordinates and source labels. Private chat text outside the quoted excerpts, raw message dumps, local file paths and protected originals are not included.",
+ "originalRequests": [
+ {
+ "id": "req-hyp990-warning-with-autofix",
+ "outcome": "a3-dead-prop-refusal",
+ "report": "hyper",
+ "sourceRecord": "A1",
+ "language": "ru",
+ "ru": "Короче думаю да надо варнинг с кнопкой auto fix через ии. Ии должен проверить и задать вопрос как лучше сделать или отменить",
+ "en": "In short, I think yes: we need a warning with an Auto fix button, through AI. The AI should check and ask a question about how best to do it, or cancel.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "5d66d8bdd6f5f86ea1ffd4dc0c1e1824c4e658b519a7340ceb8d1436d1158cc6",
+ "messageSha256": "0dec09e9a155f89651bcd1c4ed965a9f90fd9a070700943c07778d9fa32dc3ae",
+ "dateUtc": "2026-07-16T18:33:06Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#9034, Telegram message id 174396",
+ "ids": {
+ "tg": "tg#9034",
+ "telegramMessageId": 174396
+ },
+ "relationKind": "inferred",
+ "relation": "By topic only (HYP-990): an edit that cannot be applied must show a warning with an Auto fix action, and the pull request does not cite this message.",
+ "windowNote": "before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": "The message opens by quoting an assistant status bullet; only the owner's reply is quoted here.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp990-native-notification",
+ "outcome": "a3-dead-prop-refusal",
+ "report": "hyper",
+ "sourceRecord": "A2",
+ "language": "ru",
+ "ru": "Перекрывает все, гора текста, текст слева от кнопки, куча пустоты \nКакая-то нестандартная нотификация. На ext надо чтобы стандартные испзровались для vscode а на saas стандартные для saas",
+ "en": "It covers everything, a huge amount of text, the text is to the left of the button, a lot of empty space. Some kind of non-standard notification. In the extension the standard VS Code ones should be used, and in SaaS the standard SaaS ones.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "75965920a90f7e06b2fc259686b8102a353747d9b596e767228eacbbee71df72",
+ "messageSha256": "eb71987e04e6357ec0c85fc72a495e915490d24c8db41f695d202caf4e4ef7f9",
+ "dateUtc": "2026-07-17T06:33:18Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#9122, Telegram message id 174497",
+ "ids": {
+ "tg": "tg#9122",
+ "telegramMessageId": 174497
+ },
+ "relationKind": "pr",
+ "relation": "The body of pull request #665 cites “CTO tg#9122/#9125” for presenting the warning through each platform's native notification.",
+ "windowNote": "before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": "The message's opening exclamation is left out; the rest is quoted exactly. The message came with a screenshot of the old in-Inspector card, which was not recovered.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp990-details-and-ai-context",
+ "outcome": "a3-dead-prop-refusal",
+ "report": "hyper",
+ "sourceRecord": "A3",
+ "language": "ru",
+ "ru": "И подумай как раскрывать длинный текст с подробностями \nЛибо разворачивать прямо с тосте либо показать ещё подробнее в баннере по клику подробнее ну и в ии нужно чтобы все подробности уходили в лютом случае",
+ "en": "And think about how to reveal the long text with details. Either expand it right in the toast, or show more in a banner when Details is clicked, and in any case the AI must receive all the details.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "f07e16a579836f600f31a2b54f36dc693d2d8b0a67f7a9ce6b6999a07aad8e19",
+ "messageSha256": "f07e16a579836f600f31a2b54f36dc693d2d8b0a67f7a9ce6b6999a07aad8e19",
+ "dateUtc": "2026-07-17T06:36:25Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#9125, Telegram message id 174500",
+ "ids": {
+ "tg": "tg#9125",
+ "telegramMessageId": 174500
+ },
+ "relationKind": "pr",
+ "relation": "The body of pull request #665 says “the AI always receives the complete structured context (CTO tg#9125)”.",
+ "windowNote": "before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp706-fallback-is-good",
+ "outcome": "a4-css-miss-inline-floor",
+ "report": "hyper",
+ "sourceRecord": "D3",
+ "language": "ru",
+ "ru": "почему отвергнут этот фолбек? Он хороший же",
+ "en": "why was this fallback rejected? It's a good one, isn't it.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "1172df26937ef3bb6387f782f4718f14f235bf683e2b72f339adbe78e6d410aa",
+ "messageSha256": "2840d656e9575bfc7070214edf879935e4ae957117210b789e40ff600775e8cf",
+ "dateUtc": "2026-06-11T11:05:19Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyper-canvas-draft",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "spec",
+ "relation": "Master spec decision D24 records this wording about the inline-style fallback.",
+ "windowNote": "11 June 2026, before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": "The line above it in the same message is a pasted assistant proposal and is not attributed to the owner. The lines after it ask for explanations and are not quoted.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp722-same-file-const",
+ "outcome": "a5-fill-color-class-sync",
+ "report": "hyper",
+ "sourceRecord": "D1",
+ "language": "ru",
+ "ru": "через twMerge надо решать проблемы только с (внешними) мастер-компонентами, если же константа с заменяемым значением в том же файле, должна работать та логика что у нас и раньше всегда была, находить значение и заменять",
+ "en": "twMerge should be used to solve problems only with (external) master components; but if the constant with the replaceable value is in the same file, the logic we always had before should work: find the value and replace it.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "a9934d5c7608051c9d71c6913b470bf4129698e7ea477c2251bd0aeb6286a9d3",
+ "messageSha256": "b1f7e6743b1bb4898765f20a46f5749c1f7557c10eab0f13dca618293080bffd",
+ "dateUtc": "2026-06-09T18:00:43Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyper-canvas-draft",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "spec",
+ "relation": "Master spec decision D25 records this rule (twMerge for external master components only; a same-file constant is found and replaced at its definition).",
+ "windowNote": "9 June 2026, before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": "The rest of the message is about message-attachment rules of another tool and is not quoted.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp722-edit-in-place",
+ "outcome": "a5-fill-color-class-sync",
+ "report": "hyper",
+ "sourceRecord": "D2",
+ "language": "ru",
+ "ru": "пользователь должен ожидать изменение по месту",
+ "en": "the user should expect the change in place.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "79260c9acdb75489d2a52506a86b5e37282a0dd387345c59f4a7d221bd11389c",
+ "messageSha256": "af48674468be79a94adf237688f1831d2fd477c22f9f322004ce21283060a0ed",
+ "dateUtc": "2026-06-08T14:50:47Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyper-canvas-draft",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "By topic (HYP-722 master-spec decision D25, in-place edits). Written about master components, the neighbouring case of the same rule.",
+ "windowNote": "8 June 2026, before the 6 August window; kept because it is the origin of the requirement",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp1420-auto-height",
+ "outcome": "t3-layout-panel-pixel-size",
+ "report": "hyper",
+ "sourceRecord": "B1",
+ "language": "ru",
+ "ru": "а еще у кнопки Get started почему-то предлагается ручное визуальное изменение высоты хотя высота у нее Auto! разберись тоже/ причем не ad hoc чисто для одного случая и только для высоты само собой",
+ "en": "And also, for the Get started button, manual visual resizing of the height is offered for some reason, even though its height is Auto! Look into that too, and not ad hoc for just one case and, of course, not only for height.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "28696fa4bf7ec1051da8c7256086bdab2b57a18d074f66e499f5a04e05eb74d9",
+ "messageSha256": "28696fa4bf7ec1051da8c7256086bdab2b57a18d074f66e499f5a04e05eb74d9",
+ "dateUtc": "2026-08-28T11:45:34Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "task",
+ "relation": "HYP-1420 “Why” says the owner reported this with a live screenshot (the screenshot was not recovered).",
+ "windowNote": null,
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp1372-old-bug-back",
+ "outcome": "t-split-first-click",
+ "report": "hyper",
+ "sourceRecord": "C1",
+ "language": "ru",
+ "ru": "А еще вернулся старый баг \nПри клике на компонент в hyper explorer не создается Сплит",
+ "en": "And another thing: an old bug has come back. When I click a component in Hyper Explorer, a split is not created.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "9eb8a44deb8c65e676ce2aebe44839463691ea0ab8f2bcaacf57659f6c6ccef6",
+ "messageSha256": "9eb8a44deb8c65e676ce2aebe44839463691ea0ab8f2bcaacf57659f6c6ccef6",
+ "dateUtc": "2026-08-27T20:58:43Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#13863, Telegram message id 182966",
+ "ids": {
+ "tg": "tg#13863",
+ "telegramMessageId": 182966
+ },
+ "relationKind": "task",
+ "relation": "HYP-1372 “Why” records this report (Telegram screenshot, Feed.tsx opened in the same group as Hyper Canvas).",
+ "windowNote": null,
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp1372-first-click",
+ "outcome": "t-split-first-click",
+ "report": "hyper",
+ "sourceRecord": "C2",
+ "language": "ru",
+ "ru": "Кстати второй подряд клик уже Сплит открывает но первый клик у меня никогда не открывает сплит/ несколько раз пробовал",
+ "en": "By the way, the second click in a row does open the split, but the first click never opens the split for me. I tried several times.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "243c6a1cf4c5ceef03dc4860a38cc6f75fb0976479de0f1a133d61f6768fcbad",
+ "messageSha256": "243c6a1cf4c5ceef03dc4860a38cc6f75fb0976479de0f1a133d61f6768fcbad",
+ "dateUtc": "2026-08-27T21:15:43Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#13879, Telegram message id 182984",
+ "ids": {
+ "tg": "tg#13879",
+ "telegramMessageId": 182984
+ },
+ "relationKind": "task",
+ "relation": "Same HYP-1372 report.",
+ "windowNote": null,
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp1372-accepted",
+ "outcome": "t-split-first-click",
+ "report": "hyper",
+ "sourceRecord": "C3",
+ "language": "ru",
+ "ru": "Принимаю",
+ "en": "I accept.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "c0a5d6e4e696d19ca9ca325106a37ae45fb430e8b7a0e15db844fe470b74a1f1",
+ "messageSha256": "098783b05319744084618099e7766e9a3cf98d245e17ebe93538a793ae1c0fff",
+ "dateUtc": "2026-08-28T08:04:58Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "bridged Telegram message tg#13924",
+ "ids": {
+ "tg": "tg#13924"
+ },
+ "relationKind": "inferred",
+ "relation": "A reply to the assistant's message tg#13906 (“After the fix: first click on a component in Ex…”). Linked by that message's text; the acceptance is the owner's, the fix claim is the assistant's.",
+ "windowNote": null,
+ "omitted": "Only the owner's one-word reply is quoted; the quoted assistant text above it is not.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-canvas-drag-complaint",
+ "outcome": "c2-verified-move",
+ "report": "hyper",
+ "sourceRecord": "E1",
+ "language": "ru",
+ "ru": "Я потаскал элементы в hyper canvas в открытом в hyper ext проекте conloca и ужаснулся как плохо это работает.",
+ "en": "I dragged elements around in Hyper Canvas, in the Conloca project open in Hyper ext, and was horrified at how badly it works.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "dc80962479f34d1bb2de55dd3c59d08e355a1825930a1bb38687d43ec905584a",
+ "messageSha256": "024830278cc22430b7bf6f224615db59dbc9816d3337d8f3180f490d09ab692f",
+ "dateUtc": "2026-09-02T20:36:21Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "By topic and date (the Canvas drag-move work, HYP-732 and HYP-1456); the message names no ticket.",
+ "windowNote": "2 September 2026",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-hyp1404-wrong-instance",
+ "outcome": "open-hyp1404",
+ "report": "hyper",
+ "sourceRecord": "B2",
+ "language": "ru",
+ "ru": "кликаю на Try Demo а выделяется Get started. Мы уже много раз решали эту проблему/ реши ее с разных сторон раз и навсегда чтобы такое было невозможно в принципе",
+ "en": "I click Try Demo and Get started gets selected. We have solved this problem many times; solve it from different sides once and for all, so that it is impossible in principle.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "dd2bd7729fd139c238730edb51df810e8d29ac66099133d94b6e41c76160ff1d",
+ "messageSha256": "013ec364d31a2f09c4268c606e11ace5235e1c1ade1ab01b6f67cde667f9aed7",
+ "dateUtc": "2026-08-28T11:44:19Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "By topic (HYP-1404: a click on one of two sibling instances selects the wrong one). The message starts with the path of a screenshot that was not recovered.",
+ "windowNote": "28 August 2026",
+ "omitted": "The path of the screenshot and the instruction about which review models to use are not quoted.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-retro-is-not-styles",
+ "outcome": "r1-retro-identity",
+ "report": "hyper",
+ "sourceRecord": "D6",
+ "language": "ru",
+ "ru": "Запомни что это ретро спек. Стили тут не причем. Про стили мастер спек.",
+ "en": "Note that this is the retro spec. Styles have nothing to do with it. Styles are in the master spec.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "5b4a24a85558e8ad5884bd743ed06a2981c30b4ec61905d759d61f516147dbee",
+ "messageSha256": "015e6f8b0a56ecdc5e3d0d1ffce0c190cd50d879c25d401087c55fde40ebb05c",
+ "dateUtc": "2026-08-18T18:11:39Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#12319, Telegram message id 180850",
+ "ids": {
+ "tg": "tg#12319",
+ "telegramMessageId": 180850
+ },
+ "relationKind": "inferred",
+ "relation": "By topic: the owner separating the retro spec from the master styles spec.",
+ "windowNote": "18 August 2026",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ }
+ ],
+ "images": [
+ {
+ "id": "pr721-before-explorer",
+ "path": "media/pr721-before-explorer.png",
+ "sha256": "93537d33d98052395a7bb977edc99abada97c5d698e504b11195d9f520039891",
+ "width": 824,
+ "height": 446,
+ "bytes": 20809,
+ "role": "before",
+ "pair": "pr721-explorer",
+ "outcome": "t-explorer-empty-state",
+ "chip": "Before",
+ "alt": "HyperIDE Explorer panel in VS Code with no folder open, before the fix: the panel body under the HYPERIDE: EXPLORER title is empty",
+ "caption": "HyperIDE Explorer with no folder open, before the fix. The panel body is empty.",
+ "roi": [
+ 90,
+ 70,
+ 700,
+ 400
+ ],
+ "scale": 1.35,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 115,
+ 165,
+ 690,
+ 390
+ ],
+ "crop_xyxy": [
+ 34,
+ 128,
+ 810,
+ 432
+ ],
+ "note": "The Explorer view body is empty: no message and no action."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr721-before.png",
+ "sha256": "4a5a405a057a77084867df53d0dd164473ab072b5ffe792eb14059be17b8a5da",
+ "width": 2400,
+ "height": 1536,
+ "bytes": 398390,
+ "source": "hyperide/hyper-saas @683b016b .github/pr-screenshots/pr721-20260814-01-before-blank-panel-spinner.png"
+ }
+ },
+ {
+ "id": "pr721-after-explorer",
+ "path": "media/pr721-after-explorer.png",
+ "sha256": "4c3ce81652b17a7e98ce63ce815522130aea9af35a89c29b82984112ec789c2c",
+ "width": 824,
+ "height": 446,
+ "bytes": 108676,
+ "role": "after",
+ "pair": "pr721-explorer",
+ "outcome": "t-explorer-empty-state",
+ "chip": "After",
+ "alt": "Same Explorer panel and crop after the fix: it says No folder opened, explains that opening a folder closes all current editors, offers an add a folder link and shows an Open Folder button",
+ "caption": "Same window, fixture and crop after the merged fix, taken from the pull request's follow-up commit with the final wording.",
+ "roi": [
+ 90,
+ 70,
+ 700,
+ 400
+ ],
+ "scale": 1.35,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 118,
+ 164,
+ 676,
+ 298
+ ],
+ "crop_xyxy": [
+ 38,
+ 127,
+ 791,
+ 308
+ ],
+ "note": "Message: “No folder opened. Opening folder will close all current editors. To keep them open, add a folder instead.”"
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 285,
+ 314,
+ 506,
+ 382
+ ],
+ "crop_xyxy": [
+ 263,
+ 329,
+ 562,
+ 421
+ ],
+ "note": "Open Folder button."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr721-after-final.png",
+ "sha256": "db843126c805832bf876915eb9da80778cea6d6945c48428215161bda2fc1fdf",
+ "width": 2400,
+ "height": 1536,
+ "bytes": 415663,
+ "source": "hyperide/hyper-saas @4fa7dd8c .github/pr-screenshots/pr721-20260814-03-followup-exact-copy-and-two-actions.png"
+ }
+ },
+ {
+ "id": "pr721-after-interim-explorer",
+ "path": "media/pr721-after-interim-explorer.png",
+ "sha256": "7790ffcee838b189899897d4a24791098113f22d5604edbcde194310f2512077",
+ "width": 824,
+ "height": 446,
+ "bytes": 79692,
+ "role": "interim-after",
+ "pair": null,
+ "outcome": "t-explorer-empty-state",
+ "chip": "Interim after",
+ "alt": "Same Explorer panel and crop at the first fix commit: an earlier wording saying HyperIDE needs a folder open, with an Open Folder button",
+ "caption": "Interim capture at the first fix commit. A follow-up commit changed this wording, so it is not the merged copy.",
+ "roi": [
+ 90,
+ 70,
+ 700,
+ 400
+ ],
+ "scale": 1.35,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 115,
+ 168,
+ 690,
+ 250
+ ],
+ "crop_xyxy": [
+ 34,
+ 132,
+ 810,
+ 243
+ ],
+ "note": "Interim message: “HyperIDE needs a folder open to preview and edit components.”"
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 285,
+ 266,
+ 505,
+ 335
+ ],
+ "crop_xyxy": [
+ 263,
+ 265,
+ 560,
+ 358
+ ],
+ "note": "Open Folder button."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr721-after-interim.png",
+ "sha256": "f85b2341c4df27f1a61addffe90e8a07f4f7085e8efc9ba3d8936782abe2377d",
+ "width": 2400,
+ "height": 1536,
+ "bytes": 409560,
+ "source": "hyperide/hyper-saas @683b016b .github/pr-screenshots/pr721-20260814-02-after-empty-state.png"
+ }
+ },
+ {
+ "id": "pr721-link-clicked-explorer",
+ "path": "media/pr721-link-clicked-explorer.png",
+ "sha256": "52b84ef535aad07b5660560b3220803a6d59c2e327c66756075922e623b34018",
+ "width": 824,
+ "height": 446,
+ "bytes": 108489,
+ "role": "stage",
+ "pair": null,
+ "outcome": "t-explorer-empty-state",
+ "chip": "Stage",
+ "alt": "Same Explorer panel after the add a folder link was clicked: the link shows a focus outline",
+ "caption": "Later stage of the same run: the add a folder link after a click.",
+ "roi": [
+ 90,
+ 70,
+ 700,
+ 400
+ ],
+ "scale": 1.35,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 385,
+ 254,
+ 546,
+ 294
+ ],
+ "crop_xyxy": [
+ 398,
+ 248,
+ 616,
+ 302
+ ],
+ "note": "The add a folder link, activated: it shows the focus outline."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 285,
+ 314,
+ 506,
+ 382
+ ],
+ "crop_xyxy": [
+ 263,
+ 329,
+ 562,
+ 421
+ ],
+ "note": "Open Folder button."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr721-after-link-clicked.png",
+ "sha256": "6438a78b2a9e33cfd2ccae7f9dabc05d1e50ac50c3c964c6c6a53f8498b31d7e",
+ "width": 2400,
+ "height": 1536,
+ "bytes": 415695,
+ "source": "hyperide/hyper-saas @4fa7dd8c .github/pr-screenshots/pr721-20260814-04-followup-addfolder-link-clicked.png"
+ }
+ },
+ {
+ "id": "pr827-before-canvas",
+ "path": "media/pr827-before-canvas.png",
+ "sha256": "1b5fb5b129f49f854d02e002e9a7c4653c273590691329501fa607900f146c08",
+ "width": 845,
+ "height": 216,
+ "bytes": 13350,
+ "role": "before",
+ "pair": "pr827-canvas",
+ "outcome": "s1-operation-authority-in-style-editor",
+ "chip": "Before",
+ "alt": "Canvas crop before the width write: element R is a narrow square",
+ "caption": "Canvas, element R, before the Inspector width write.",
+ "roi": [
+ 760,
+ 312,
+ 1112,
+ 402
+ ],
+ "scale": 2.4,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 768,
+ 324,
+ 824,
+ 379
+ ],
+ "crop_xyxy": [
+ 19,
+ 29,
+ 154,
+ 161
+ ],
+ "note": "Element R is the narrow square, Tailwind w-12 (3rem)."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr827-before.png",
+ "sha256": "199847407455ebf23115c639f0439f487e34fcb904137595d80de1b47cc731c2",
+ "width": 1440,
+ "height": 835,
+ "bytes": 259980,
+ "source": "hyperide/hyper-saas @57ce3969 docs/reports/evidence/style-operation-20261016/native-before.png"
+ }
+ },
+ {
+ "id": "pr827-after-canvas",
+ "path": "media/pr827-after-canvas.png",
+ "sha256": "d948af0054b08bd0c68cc2e7fabc848b7a12d9236ac0bbf5bdf1b98598fed978",
+ "width": 845,
+ "height": 216,
+ "bytes": 13466,
+ "role": "after",
+ "pair": "pr827-canvas",
+ "outcome": "s1-operation-authority-in-style-editor",
+ "chip": "After",
+ "alt": "Same canvas crop after the width write: element R is clearly wider",
+ "caption": "Same element and crop after the write.",
+ "roi": [
+ 760,
+ 312,
+ 1112,
+ 402
+ ],
+ "scale": 2.4,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 768,
+ 324,
+ 916,
+ 379
+ ],
+ "crop_xyxy": [
+ 19,
+ 29,
+ 374,
+ 161
+ ],
+ "note": "Element R is wider after the write; the pull request measures 140 px."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr827-after.png",
+ "sha256": "ffb57145c8fb2fc28e98c08797cc82a68ac2c169b67a9b91d0c120957ef692aa",
+ "width": 1440,
+ "height": 835,
+ "bytes": 260507,
+ "source": "hyperide/hyper-saas @57ce3969 docs/reports/evidence/style-operation-20261016/native-after.png"
+ }
+ },
+ {
+ "id": "pr827-before-inspector",
+ "path": "media/pr827-before-inspector.png",
+ "sha256": "233c1fdd86038e1505ac0301b7ac5ace0c81e868e77b49cbf21cc540b21405ca",
+ "width": 804,
+ "height": 132,
+ "bytes": 21197,
+ "role": "before",
+ "pair": "pr827-inspector",
+ "outcome": "s1-operation-authority-in-style-editor",
+ "chip": "Before",
+ "alt": "Inspector Layout row crop before the write: the width field reads 3rem",
+ "caption": "Inspector Layout row, width and height fields, before the write.",
+ "roi": [
+ 1150,
+ 468,
+ 1418,
+ 512
+ ],
+ "scale": 3.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1156,
+ 476,
+ 1268,
+ 505
+ ],
+ "crop_xyxy": [
+ 18,
+ 24,
+ 354,
+ 111
+ ],
+ "note": "The W field reads 3rem."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr827-before.png",
+ "sha256": "199847407455ebf23115c639f0439f487e34fcb904137595d80de1b47cc731c2",
+ "width": 1440,
+ "height": 835,
+ "bytes": 259980,
+ "source": "hyperide/hyper-saas @57ce3969 docs/reports/evidence/style-operation-20261016/native-before.png"
+ }
+ },
+ {
+ "id": "pr827-after-inspector",
+ "path": "media/pr827-after-inspector.png",
+ "sha256": "58718488b7b6e7a538e9fb74feb0f0b39c51a75837157d6e43072f0591154e2a",
+ "width": 804,
+ "height": 132,
+ "bytes": 16022,
+ "role": "after",
+ "pair": "pr827-inspector",
+ "outcome": "s1-operation-authority-in-style-editor",
+ "chip": "After",
+ "alt": "Same Inspector Layout row crop after the write: the width field reads 140",
+ "caption": "Same fields after the write: width shows the new value.",
+ "roi": [
+ 1150,
+ 468,
+ 1418,
+ 512
+ ],
+ "scale": 3.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1156,
+ 476,
+ 1268,
+ 505
+ ],
+ "crop_xyxy": [
+ 18,
+ 24,
+ 354,
+ 111
+ ],
+ "note": "The W field reads 140."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr827-after.png",
+ "sha256": "ffb57145c8fb2fc28e98c08797cc82a68ac2c169b67a9b91d0c120957ef692aa",
+ "width": 1440,
+ "height": 835,
+ "bytes": 260507,
+ "source": "hyperide/hyper-saas @57ce3969 docs/reports/evidence/style-operation-20261016/native-after.png"
+ }
+ },
+ {
+ "id": "pr665-after-native-warning",
+ "path": "media/pr665-after-native-warning.png",
+ "sha256": "e1df364477a43431212a6416d46814608c886893546156f9da8dc8226f6a6315",
+ "width": 950,
+ "height": 258,
+ "bytes": 41388,
+ "role": "single-after",
+ "pair": null,
+ "outcome": "a3-dead-prop-refusal",
+ "chip": "After (single capture)",
+ "alt": "Native VS Code warning notification: Style could not be applied, Badge does not forward this prop to the DOM, with Details and Auto fix via AI buttons",
+ "caption": "Native VS Code warning after a border-radius edit on a Badge component that forwards neither className nor style.",
+ "roi": [
+ 1930,
+ 1452,
+ 2880,
+ 1710
+ ],
+ "scale": 1.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1975,
+ 1476,
+ 2775,
+ 1590
+ ],
+ "crop_xyxy": [
+ 45,
+ 24,
+ 845,
+ 138
+ ],
+ "note": "Warning text: Style could not be applied, and does not forward this prop to the DOM."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 2515,
+ 1596,
+ 2855,
+ 1664
+ ],
+ "crop_xyxy": [
+ 585,
+ 144,
+ 925,
+ 212
+ ],
+ "note": "Details and Auto fix via AI buttons."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr665-after.png",
+ "sha256": "ac639d764c014623f3deb5681f8c4fe187cf6bf67b074c8130f9ee47cb06ad31",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 568690,
+ "source": "image embedded in the body of hyperide/hyper-saas#665"
+ }
+ },
+ {
+ "id": "pr738-after-inspector-warning",
+ "path": "media/pr738-after-inspector-warning.png",
+ "sha256": "3d65de2c7371520564637bd5d7ecc77d140ed7c2ec344625dfe45cdeb4542580",
+ "width": 810,
+ "height": 716,
+ "bytes": 185949,
+ "role": "single-after",
+ "pair": null,
+ "outcome": "a2-no-style-surface-warning",
+ "chip": "After (single capture)",
+ "alt": "Inspector toast in VS Code: This component may not accept style edits, NonForwardingBadge does not appear to forward className or style to a DOM element, with an Ask AI action",
+ "caption": "Inspector after selecting a component that does not forward className or style.",
+ "roi": [
+ 2280,
+ 190,
+ 2880,
+ 720
+ ],
+ "scale": 1.35,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 2345,
+ 255,
+ 2700,
+ 640
+ ],
+ "crop_xyxy": [
+ 88,
+ 88,
+ 567,
+ 608
+ ],
+ "note": "Warning: “This component may not accept style edits”, with the reason."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 2715,
+ 415,
+ 2856,
+ 490
+ ],
+ "crop_xyxy": [
+ 587,
+ 304,
+ 778,
+ 405
+ ],
+ "note": "Ask AI action."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr738-after.png",
+ "sha256": "a35de0a9f5a2431965b244f85235c5249564d88b032363c96e2c47ff4547b685",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 436954,
+ "source": "image attached to hyperide/hyper-saas#738"
+ }
+ },
+ {
+ "id": "pr738-after-canvas-selection",
+ "path": "media/pr738-after-canvas-selection.png",
+ "sha256": "54138354689329d47a352dd8559e20bf3bfa252dd32a0142f188a8d314781b9f",
+ "width": 800,
+ "height": 280,
+ "bytes": 28952,
+ "role": "context",
+ "pair": null,
+ "outcome": "a2-no-style-surface-warning",
+ "chip": "Same capture",
+ "alt": "Canvas region of the same capture: the selected element has a blue selection outline",
+ "caption": "The element selected in the Canvas in the same capture.",
+ "roi": [
+ 1500,
+ 1340,
+ 1900,
+ 1480
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1548,
+ 1388,
+ 1762,
+ 1442
+ ],
+ "crop_xyxy": [
+ 96,
+ 96,
+ 524,
+ 204
+ ],
+ "note": "The selected element, labelled “No style surface”, with a blue selection outline."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr738-after.png",
+ "sha256": "a35de0a9f5a2431965b244f85235c5249564d88b032363c96e2c47ff4547b685",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 436954,
+ "source": "image attached to hyperide/hyper-saas#738"
+ }
+ },
+ {
+ "id": "pr794-before-tab",
+ "path": "media/pr794-before-tab.png",
+ "sha256": "ae04c8ded2d66fc6baecf3aab07be80994c5ca36c7d6c84152352b3f041e751b",
+ "width": 794,
+ "height": 112,
+ "bytes": 31489,
+ "role": "before",
+ "pair": "pr794-tab",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "Before",
+ "alt": "Editor tab strip before Edit component: the active tab is App.tsx",
+ "caption": "Editor tabs before Edit component, with the Feed call-site selected inside App.",
+ "roi": [
+ 348,
+ 40,
+ 745,
+ 96
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 352,
+ 42,
+ 462,
+ 66
+ ],
+ "crop_xyxy": [
+ 8,
+ 4,
+ 228,
+ 52
+ ],
+ "note": "Active editor tab: App.tsx."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 362,
+ 72,
+ 530,
+ 91
+ ],
+ "crop_xyxy": [
+ 28,
+ 64,
+ 364,
+ 102
+ ],
+ "note": "Breadcrumb ends at App.tsx."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-before.png",
+ "sha256": "bc1e8d9d542b8b336f930fed8fd65219affcf28eca1b082315da6457de4557d5",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 264064,
+ "source": "image in the body of hyperide/hyper-saas#794 (Before)"
+ }
+ },
+ {
+ "id": "pr794-after-tab",
+ "path": "media/pr794-after-tab.png",
+ "sha256": "964e95887cc6d53adf7b7508c1e93b52b49fb233a7a513460efc24ceafe991e4",
+ "width": 794,
+ "height": 112,
+ "bytes": 39849,
+ "role": "after",
+ "pair": "pr794-tab",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "After",
+ "alt": "Same tab strip after Edit component: the active tab is Feed.tsx and the breadcrumb reads src, components, Feed.tsx",
+ "caption": "Same crop after Edit component: the editor opened the Feed definition.",
+ "roi": [
+ 348,
+ 40,
+ 745,
+ 96
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 466,
+ 42,
+ 584,
+ 66
+ ],
+ "crop_xyxy": [
+ 236,
+ 4,
+ 472,
+ 52
+ ],
+ "note": "Active editor tab: Feed.tsx."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 362,
+ 72,
+ 592,
+ 91
+ ],
+ "crop_xyxy": [
+ 28,
+ 64,
+ 488,
+ 102
+ ],
+ "note": "Breadcrumb: src › components › Feed.tsx."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-after.png",
+ "sha256": "0e7f863a1a9223f9addd74bcdbcecd88bd5b3b2a097cc6c2e0e1f79648e1a4fc",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 352024,
+ "source": "image in the body of hyperide/hyper-saas#794 (After)"
+ }
+ },
+ {
+ "id": "pr794-before-canvas",
+ "path": "media/pr794-before-canvas.png",
+ "sha256": "b4213372739a2eec11c334fa7a46440b0969bb2a9915c78bff422b0e407d4544",
+ "width": 790,
+ "height": 580,
+ "bytes": 134483,
+ "role": "before",
+ "pair": "pr794-canvas",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "Before",
+ "alt": "Canvas before Edit component: the whole App page with its top bar, and the Feed call-site outlined in blue",
+ "caption": "Canvas before: the whole App page, with Feed selected inside it.",
+ "roi": [
+ 745,
+ 40,
+ 1140,
+ 330
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 763,
+ 122,
+ 1016,
+ 325
+ ],
+ "crop_xyxy": [
+ 36,
+ 164,
+ 542,
+ 570
+ ],
+ "note": "Blue outline: the Feed call-site, selected inside the App page."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 762,
+ 76,
+ 1132,
+ 118
+ ],
+ "crop_xyxy": [
+ 34,
+ 72,
+ 774,
+ 156
+ ],
+ "note": "App's own top bar is still part of what the Canvas renders."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-before.png",
+ "sha256": "bc1e8d9d542b8b336f930fed8fd65219affcf28eca1b082315da6457de4557d5",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 264064,
+ "source": "image in the body of hyperide/hyper-saas#794 (Before)"
+ }
+ },
+ {
+ "id": "pr794-after-canvas",
+ "path": "media/pr794-after-canvas.png",
+ "sha256": "af3a83ee3a0c8671c41af1b881300821741ced5a75a19f39f8d2c4cdce4bb532",
+ "width": 790,
+ "height": 580,
+ "bytes": 190014,
+ "role": "after",
+ "pair": "pr794-canvas",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "After",
+ "alt": "Same canvas crop after Edit component: the Canvas shows the Feed definition on its own, starting at the Home heading, without App's top bar",
+ "caption": "Same crop after: the Canvas switched to the Feed definition.",
+ "roi": [
+ 745,
+ 40,
+ 1140,
+ 330
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 770,
+ 78,
+ 856,
+ 114
+ ],
+ "crop_xyxy": [
+ 50,
+ 76,
+ 222,
+ 148
+ ],
+ "note": "The Canvas renders the Feed definition on its own, starting at its Home heading. App's top bar is gone."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-after.png",
+ "sha256": "0e7f863a1a9223f9addd74bcdbcecd88bd5b3b2a097cc6c2e0e1f79648e1a4fc",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 352024,
+ "source": "image in the body of hyperide/hyper-saas#794 (After)"
+ }
+ },
+ {
+ "id": "pr794-before-tree",
+ "path": "media/pr794-before-tree.png",
+ "sha256": "e90362a291ac0b5beed42b4921c06c62a6bd6d8d33bd52c0105380a428e96fda",
+ "width": 780,
+ "height": 775,
+ "bytes": 127318,
+ "role": "before",
+ "pair": "pr794-tree",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "Before",
+ "alt": "Elements tree before Edit component: the Feed call-site row is selected",
+ "caption": "Elements tree before: the Feed call-site row is selected.",
+ "roi": [
+ 48,
+ 664,
+ 348,
+ 962
+ ],
+ "scale": 2.6,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 56,
+ 856,
+ 340,
+ 882
+ ],
+ "crop_xyxy": [
+ 21,
+ 499,
+ 759,
+ 567
+ ],
+ "note": "Selected row: Feed, a call-site inside App."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-before.png",
+ "sha256": "bc1e8d9d542b8b336f930fed8fd65219affcf28eca1b082315da6457de4557d5",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 264064,
+ "source": "image in the body of hyperide/hyper-saas#794 (Before)"
+ }
+ },
+ {
+ "id": "pr794-after-tree",
+ "path": "media/pr794-after-tree.png",
+ "sha256": "0da2163b58c93af9e491264d2c8eb068994a2e1eef921b0048e13609bec1612e",
+ "width": 780,
+ "height": 775,
+ "bytes": 152407,
+ "role": "after",
+ "pair": "pr794-tree",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "After",
+ "alt": "Same Elements tree crop after Edit component: the tree is rebuilt from the Feed definition and starts at main, div, h1 Home",
+ "caption": "Same crop after: the tree is rebuilt from the Feed definition.",
+ "roi": [
+ 48,
+ 664,
+ 348,
+ 962
+ ],
+ "scale": 2.6,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 56,
+ 684,
+ 340,
+ 758
+ ],
+ "crop_xyxy": [
+ 21,
+ 52,
+ 759,
+ 244
+ ],
+ "note": "The tree now starts at the root of Feed's own source, main › div › h1 Home."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-after.png",
+ "sha256": "0e7f863a1a9223f9addd74bcdbcecd88bd5b3b2a097cc6c2e0e1f79648e1a4fc",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 352024,
+ "source": "image in the body of hyperide/hyper-saas#794 (After)"
+ }
+ },
+ {
+ "id": "pr794-before-notice",
+ "path": "media/pr794-before-notice.png",
+ "sha256": "a1122c563e31588fb19b98bd4fe5d009344bef23ac0a9d62755b6b66360ef413",
+ "width": 790,
+ "height": 167,
+ "bytes": 40103,
+ "role": "before",
+ "pair": "pr794-notice",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "Before",
+ "alt": "Bottom-right notices before Edit component: only the dev server notice",
+ "caption": "Notices before: only the dev server notice.",
+ "roi": [
+ 975,
+ 878,
+ 1440,
+ 976
+ ],
+ "scale": 1.7,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 982,
+ 930,
+ 1436,
+ 970
+ ],
+ "crop_xyxy": [
+ 12,
+ 88,
+ 784,
+ 156
+ ],
+ "note": "Only notice: “Dev server running at localhost”."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-before.png",
+ "sha256": "bc1e8d9d542b8b336f930fed8fd65219affcf28eca1b082315da6457de4557d5",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 264064,
+ "source": "image in the body of hyperide/hyper-saas#794 (Before)"
+ }
+ },
+ {
+ "id": "pr794-after-notice",
+ "path": "media/pr794-after-notice.png",
+ "sha256": "92e97c5335a1546ca017aff6730450e0a3722dd9df5389533891c24f8559a459",
+ "width": 790,
+ "height": 167,
+ "bytes": 62180,
+ "role": "after",
+ "pair": "pr794-notice",
+ "outcome": "c5-edit-component-drag-undo",
+ "chip": "After",
+ "alt": "Same notices crop after Edit component: a new notice says Editing Feed and that changes to this component definition affect all its instances",
+ "caption": "Same crop after: a notice explains the scope of editing a definition.",
+ "roi": [
+ 975,
+ 878,
+ 1440,
+ 976
+ ],
+ "scale": 1.7,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 982,
+ 890,
+ 1436,
+ 920
+ ],
+ "crop_xyxy": [
+ 12,
+ 20,
+ 784,
+ 71
+ ],
+ "note": "New notice: “Editing Feed. Changes to this component definition affect all its instances.”"
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr794-after.png",
+ "sha256": "0e7f863a1a9223f9addd74bcdbcecd88bd5b3b2a097cc6c2e0e1f79648e1a4fc",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 352024,
+ "source": "image in the body of hyperide/hyper-saas#794 (After)"
+ }
+ },
+ {
+ "id": "pr805-step1-edit-cancelled",
+ "path": "media/pr805-step1-edit-cancelled.png",
+ "sha256": "dad2503f6001f1c48b4ea1683028b6c7dd1a4ad761300221732ced618843ebe1",
+ "width": 783,
+ "height": 551,
+ "bytes": 180827,
+ "role": "step-1",
+ "pair": "pr805-sequence",
+ "outcome": "c1-stale-context-cancellation",
+ "chip": "Step 1",
+ "alt": "Inspector panel with a message: Edit cancelled. The selected element or preview changed before this edit was applied. Select the element and try again",
+ "caption": "Step 1, Inspector: the selection changed before a delayed edit was dispatched, and the edit is refused.",
+ "roi": [
+ 1146,
+ 96,
+ 1436,
+ 300
+ ],
+ "scale": 2.7,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1156,
+ 106,
+ 1426,
+ 284
+ ],
+ "crop_xyxy": [
+ 27,
+ 27,
+ 756,
+ 508
+ ],
+ "note": "Inspector: “Edit cancelled. The selected element or preview changed before this edit was applied. Select the element and try again. (src/ContextGuardFixture.tsx)”"
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr805-edit-cancelled.png",
+ "sha256": "8af51db9f1c7f051d995a2914b55c3cb04b2c93cf5fff9d163aa6fb241be958e",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 172684,
+ "source": "image in the body of hyperide/hyper-saas#805 (HYP-1480 criterion 1)"
+ }
+ },
+ {
+ "id": "pr805-step1-selection-kept",
+ "path": "media/pr805-step1-selection-kept.png",
+ "sha256": "cc023ac804c7ce2f868c5b01bf246cf87975fab2639d3880d120e113f5bcfdcf",
+ "width": 803,
+ "height": 550,
+ "bytes": 139679,
+ "role": "step-1",
+ "pair": "pr805-sequence",
+ "outcome": "c1-stale-context-cancellation",
+ "chip": "Step 1",
+ "alt": "Canvas region of the same capture: the second heading stays selected with a blue outline",
+ "caption": "Step 1, Canvas, same capture: the second heading stays selected after the cancellation.",
+ "roi": [
+ 765,
+ 70,
+ 1130,
+ 320
+ ],
+ "scale": 2.2,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 790,
+ 204,
+ 1030,
+ 244
+ ],
+ "crop_xyxy": [
+ 55,
+ 295,
+ 583,
+ 383
+ ],
+ "note": "The second heading, “Second editable heading”, stays selected."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr805-edit-cancelled.png",
+ "sha256": "8af51db9f1c7f051d995a2914b55c3cb04b2c93cf5fff9d163aa6fb241be958e",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 172684,
+ "source": "image in the body of hyperide/hyper-saas#805 (HYP-1480 criterion 1)"
+ }
+ },
+ {
+ "id": "pr805-step2-next-edit-persisted",
+ "path": "media/pr805-step2-next-edit-persisted.png",
+ "sha256": "7c0e2ff68f1fd8ed20e3663b610e7f90f8cf9d31060b405223df05ae24ce7b41",
+ "width": 803,
+ "height": 550,
+ "bytes": 100104,
+ "role": "step-2",
+ "pair": "pr805-sequence",
+ "outcome": "c1-stale-context-cancellation",
+ "chip": "Step 2",
+ "alt": "Canvas region at a later stage of the same run: the first heading is almost transparent, because the next ordinary Inspector edit was applied",
+ "caption": "Step 2, same run: the next ordinary edit persisted. The test typed a near-zero opacity, so the heading is almost invisible.",
+ "roi": [
+ 765,
+ 70,
+ 1130,
+ 320
+ ],
+ "scale": 2.2,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 790,
+ 98,
+ 1000,
+ 170
+ ],
+ "crop_xyxy": [
+ 55,
+ 62,
+ 517,
+ 220
+ ],
+ "note": "The first heading is almost transparent: the next Inspector edit was applied."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr805-next-edit.png",
+ "sha256": "a1f4bbfa86041f9405149c08680eef1ab0b01155499ad62bbd2f635be167597c",
+ "width": 1440,
+ "height": 1000,
+ "bytes": 154508,
+ "source": "image in the body of hyperide/hyper-saas#805 (HYP-1480 criterion 2)"
+ }
+ },
+ {
+ "id": "pr811-before-order-row",
+ "path": "media/pr811-before-order-row.png",
+ "sha256": "5c1f6429d10732f848058707b1892834b1646143b432dc1ec842bf50a6102bfe",
+ "width": 816,
+ "height": 238,
+ "bytes": 46764,
+ "role": "before",
+ "pair": "pr811-order-row",
+ "outcome": "c2-verified-move",
+ "chip": "Before",
+ "alt": "Canvas crop before the order drop: the row of chips reads Order A, Order B, Order C",
+ "caption": "Canvas, the order row, before Order A is dropped after Order C.",
+ "roi": [
+ 1500,
+ 790,
+ 1980,
+ 930
+ ],
+ "scale": 1.7,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1540,
+ 862,
+ 1935,
+ 912
+ ],
+ "crop_xyxy": [
+ 68,
+ 122,
+ 740,
+ 207
+ ],
+ "note": "The order row reads Order A, Order B, Order C."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr811-order-before.png",
+ "sha256": "5f6df6841c19a430a04f29b8a608dc1635f6762a12b4de5fc6fe7c81f0a8e7f8",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 631230,
+ "source": "image in the body of hyperide/hyper-saas#811 (hyp732-r2-5-order-before)"
+ }
+ },
+ {
+ "id": "pr811-after-order-row",
+ "path": "media/pr811-after-order-row.png",
+ "sha256": "38037a9462ea14c31b6b7100f92a85238fb99acc1351e967f9e70aa048178434",
+ "width": 816,
+ "height": 238,
+ "bytes": 46955,
+ "role": "after",
+ "pair": "pr811-order-row",
+ "outcome": "c2-verified-move",
+ "chip": "After",
+ "alt": "Same canvas crop after the order drop: the row of chips reads Order B, Order C, Order A, with Order A outlined as selected",
+ "caption": "Same crop after the drop: the visual order is B, C, A.",
+ "roi": [
+ 1500,
+ 790,
+ 1980,
+ 930
+ ],
+ "scale": 1.7,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1540,
+ 862,
+ 1935,
+ 912
+ ],
+ "crop_xyxy": [
+ 68,
+ 122,
+ 740,
+ 207
+ ],
+ "note": "The row now reads Order B, Order C, Order A, and Order A is selected."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr811-order-after.png",
+ "sha256": "6ddb11e8f241a44cd5ac2353b151d440c2a640dd8595b37a433c32bd6a3bcc03",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 568535,
+ "source": "image in the body of hyperide/hyper-saas#811 (hyp732-r2-5-order-after)"
+ }
+ },
+ {
+ "id": "pr811-before-order-inspector",
+ "path": "media/pr811-before-order-inspector.png",
+ "sha256": "9c620df90389ceac4d587af7e2e7a8c1f11bdea37f268c71e0cdf9eb90334f61",
+ "width": 798,
+ "height": 504,
+ "bytes": 72545,
+ "role": "before",
+ "pair": "pr811-order-inspector",
+ "outcome": "c2-verified-move",
+ "chip": "Before",
+ "alt": "Inspector crop before the order drop: it reads No element selected",
+ "caption": "Inspector before the drop.",
+ "roi": [
+ 2290,
+ 120,
+ 2860,
+ 480
+ ],
+ "scale": 1.4,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 2300,
+ 330,
+ 2850,
+ 470
+ ],
+ "crop_xyxy": [
+ 14,
+ 294,
+ 784,
+ 490
+ ],
+ "note": "Inspector: “No element selected”."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr811-order-before.png",
+ "sha256": "5f6df6841c19a430a04f29b8a608dc1635f6762a12b4de5fc6fe7c81f0a8e7f8",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 631230,
+ "source": "image in the body of hyperide/hyper-saas#811 (hyp732-r2-5-order-before)"
+ }
+ },
+ {
+ "id": "pr811-after-order-inspector",
+ "path": "media/pr811-after-order-inspector.png",
+ "sha256": "66ba864bd264985b790d9c672ee37fdf257394a2929a4dfe7df75442f36cca98",
+ "width": 798,
+ "height": 504,
+ "bytes": 61097,
+ "role": "after",
+ "pair": "pr811-order-inspector",
+ "outcome": "c2-verified-move",
+ "chip": "After",
+ "alt": "Same Inspector crop after the order drop: it shows Frame (div) named Order A",
+ "caption": "Same crop after the drop: the Inspector follows the moved element.",
+ "roi": [
+ 2290,
+ 120,
+ 2860,
+ 480
+ ],
+ "scale": 1.4,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 2295,
+ 215,
+ 2850,
+ 335
+ ],
+ "crop_xyxy": [
+ 7,
+ 133,
+ 784,
+ 301
+ ],
+ "note": "Inspector: Frame (div), named Order A, the moved element."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr811-order-after.png",
+ "sha256": "6ddb11e8f241a44cd5ac2353b151d440c2a640dd8595b37a433c32bd6a3bcc03",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 568535,
+ "source": "image in the body of hyperide/hyper-saas#811 (hyp732-r2-5-order-after)"
+ }
+ },
+ {
+ "id": "pr811-restored-notice",
+ "path": "media/pr811-restored-notice.png",
+ "sha256": "1fad2f995bb497a710b14643e779a84c0f9650795d3963902ea878a0baf74f47",
+ "width": 940,
+ "height": 330,
+ "bytes": 81307,
+ "role": "single-after",
+ "pair": null,
+ "outcome": "c2-verified-move",
+ "chip": "After (single capture)",
+ "alt": "VS Code notice: Canvas move did not verify and was restored. The preview did not render the moved version of TestElements.tsx. The verification deadline passed. An AI recovery proposal was queued in the AI Chat panel, with an Open AI Chat button",
+ "caption": "A move whose target module throws while loading: the file is restored and the notice says so.",
+ "roi": [
+ 1940,
+ 1370,
+ 2880,
+ 1700
+ ],
+ "scale": 1.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 1990,
+ 1395,
+ 2800,
+ 1585
+ ],
+ "crop_xyxy": [
+ 50,
+ 25,
+ 860,
+ 215
+ ],
+ "note": "Notice: the move did not verify and was restored; an AI recovery proposal was queued."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 2650,
+ 1600,
+ 2850,
+ 1655
+ ],
+ "crop_xyxy": [
+ 710,
+ 230,
+ 910,
+ 285
+ ],
+ "note": "Open AI Chat button."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr811-restored-toast.png",
+ "sha256": "3e64de85fd8681034f17d68151802a4383b4cd66ad274c268f57b578e017a93f",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 611670,
+ "source": "image in the body of hyperide/hyper-saas#811 (hyp732-r2-2-restored-toast)"
+ }
+ },
+ {
+ "id": "pr823-restored-fix-with-ai",
+ "path": "media/pr823-restored-fix-with-ai.png",
+ "sha256": "071938a657a9724622ea7e29161cd89f8f884e0c07cca7f53ccba34ae4cfc6f7",
+ "width": 960,
+ "height": 230,
+ "bytes": 78162,
+ "role": "single-after",
+ "pair": null,
+ "outcome": "c3-ai-fix-on-click-only",
+ "chip": "After (single capture)",
+ "alt": "VS Code notice: Canvas couldn't apply the move and put the file back, with Fix with AI and Details buttons",
+ "caption": "After a failed move the notice offers Fix with AI as a button.",
+ "roi": [
+ 960,
+ 705,
+ 1440,
+ 820
+ ],
+ "scale": 2.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 988,
+ 726,
+ 1330,
+ 756
+ ],
+ "crop_xyxy": [
+ 56,
+ 42,
+ 740,
+ 102
+ ],
+ "note": "Notice: “Canvas couldn't apply the move and put the file back.”"
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 1280,
+ 762,
+ 1426,
+ 795
+ ],
+ "crop_xyxy": [
+ 640,
+ 114,
+ 932,
+ 180
+ ],
+ "note": "Fix with AI and Details are buttons in the notice."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr823-restored-toast.png",
+ "sha256": "122365e4b6f573229dfd6617ead6656616ce4f04f8db856a8bde22cdf0a31af2",
+ "width": 1440,
+ "height": 835,
+ "bytes": 277659,
+ "source": "hyperide/hyper-saas @a4caec76 docs/notes/canvas-proof-20261016/restored.png"
+ }
+ },
+ {
+ "id": "pr705-step1-dialog",
+ "path": "media/pr705-step1-dialog.png",
+ "sha256": "1ba03f1ca8b147b0570353db9470c638c3451d0705dbe0fcb8bdb1638011cb82",
+ "width": 793,
+ "height": 1710,
+ "bytes": 329682,
+ "role": "step-1",
+ "pair": "pr705-sequence",
+ "outcome": "t-new-component-dialog",
+ "chip": "Step 1",
+ "alt": "The guided New component dialog in VS Code: three kinds in plain words, a name field with HeroBadge and the target folder, and Create and Cancel buttons",
+ "caption": "Step 1: the guided New component dialog, opened from the Components + button.",
+ "roi": [
+ 90,
+ 260,
+ 700,
+ 1575
+ ],
+ "scale": 1.3,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 145,
+ 517,
+ 646,
+ 1084
+ ],
+ "crop_xyxy": [
+ 72,
+ 334,
+ 723,
+ 1071
+ ],
+ "note": "Three kinds in plain words: Building block, Section, Page."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 140,
+ 1163,
+ 653,
+ 1259
+ ],
+ "crop_xyxy": [
+ 65,
+ 1174,
+ 732,
+ 1299
+ ],
+ "note": "Name field, filled with HeroBadge, with the target folder hint."
+ },
+ {
+ "n": 3,
+ "original_xyxy": [
+ 145,
+ 1356,
+ 646,
+ 1440
+ ],
+ "crop_xyxy": [
+ 72,
+ 1425,
+ 723,
+ 1534
+ ],
+ "note": "The Create button; Cancel sits below it."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr705-dialog.png",
+ "sha256": "ee77f190d5bbac3e30df5ed2530c912f36ce5504b3488ad62e3d52bcb9588655",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 721362,
+ "source": "hyperide/hyper-saas @b901fb76 .github/pr-screenshots/20260806-hyp1184-create-dialog.png"
+ }
+ },
+ {
+ "id": "pr705-step2-created-list",
+ "path": "media/pr705-step2-created-list.png",
+ "sha256": "4bb28b1bd3e69976fc2a6d50f9dfd32d1ac1d2f8d4b47f9daae107000662a6f1",
+ "width": 793,
+ "height": 494,
+ "bytes": 122964,
+ "role": "step-2",
+ "pair": "pr705-sequence",
+ "outcome": "t-new-component-dialog",
+ "chip": "Step 2",
+ "alt": "Components list after Create: HeroBadge.tsx appears in Composite components and is selected, and the Elements tree shows its root",
+ "caption": "Step 2: after Create, the new file is listed and opened.",
+ "roi": [
+ 90,
+ 880,
+ 700,
+ 1260
+ ],
+ "scale": 1.3,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 160,
+ 904,
+ 680,
+ 962
+ ],
+ "crop_xyxy": [
+ 91,
+ 31,
+ 767,
+ 107
+ ],
+ "note": "HeroBadge.tsx now appears in Composite components, selected."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 95,
+ 1196,
+ 420,
+ 1244
+ ],
+ "crop_xyxy": [
+ 6,
+ 411,
+ 429,
+ 473
+ ],
+ "note": "The Elements tree shows the new component's root."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr705-created.png",
+ "sha256": "0472c6b2aa0868b52e7980de8ba2c7989994513544b8dfeb865832d704fafe2d",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 323033,
+ "source": "hyperide/hyper-saas @b901fb76 .github/pr-screenshots/20260806-hyp1184-created-in-preview.png"
+ }
+ },
+ {
+ "id": "pr705-step2-created-canvas",
+ "path": "media/pr705-step2-created-canvas.png",
+ "sha256": "a2544a2f7c5eb947914be58f8a792206b0a17057ad8f89f2621d896037031c02",
+ "width": 950,
+ "height": 190,
+ "bytes": 41815,
+ "role": "step-2",
+ "pair": "pr705-sequence",
+ "outcome": "t-new-component-dialog",
+ "chip": "Step 2",
+ "alt": "Editor tab HeroBadge.tsx and the Hyper Canvas rendering the Hero Badge component",
+ "caption": "Step 2, same capture: the new component opens in the editor and renders in the Canvas.",
+ "roi": [
+ 900,
+ 70,
+ 1850,
+ 260
+ ],
+ "scale": 1.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 935,
+ 76,
+ 1240,
+ 134
+ ],
+ "crop_xyxy": [
+ 35,
+ 6,
+ 340,
+ 64
+ ],
+ "note": "Editor tab HeroBadge.tsx."
+ },
+ {
+ "n": 2,
+ "original_xyxy": [
+ 1540,
+ 186,
+ 1732,
+ 238
+ ],
+ "crop_xyxy": [
+ 640,
+ 116,
+ 832,
+ 168
+ ],
+ "note": "The Canvas renders Hero Badge."
+ }
+ ],
+ "original": {
+ "path": "media/orig/pr705-created.png",
+ "sha256": "0472c6b2aa0868b52e7980de8ba2c7989994513544b8dfeb865832d704fafe2d",
+ "width": 2880,
+ "height": 1736,
+ "bytes": 323033,
+ "source": "hyperide/hyper-saas @b901fb76 .github/pr-screenshots/20260806-hyp1184-created-in-preview.png"
+ }
+ }
+ ],
+ "groups": [
+ {
+ "id": "pr721-explorer",
+ "kind": "pair",
+ "crops": [
+ "pr721-before-explorer",
+ "pr721-after-explorer",
+ "pr721-after-interim-explorer",
+ "pr721-link-clicked-explorer"
+ ]
+ },
+ {
+ "id": "pr827-write",
+ "kind": "pair",
+ "crops": [
+ "pr827-before-canvas",
+ "pr827-after-canvas",
+ "pr827-before-inspector",
+ "pr827-after-inspector"
+ ]
+ },
+ {
+ "id": "pr665-warning",
+ "kind": "single",
+ "crops": [
+ "pr665-after-native-warning"
+ ]
+ },
+ {
+ "id": "pr738-warning",
+ "kind": "single",
+ "crops": [
+ "pr738-after-inspector-warning",
+ "pr738-after-canvas-selection"
+ ]
+ },
+ {
+ "id": "pr794-edit-component",
+ "kind": "pair",
+ "crops": [
+ "pr794-before-tab",
+ "pr794-after-tab",
+ "pr794-before-canvas",
+ "pr794-after-canvas",
+ "pr794-before-tree",
+ "pr794-after-tree",
+ "pr794-before-notice",
+ "pr794-after-notice"
+ ]
+ },
+ {
+ "id": "pr805-sequence",
+ "kind": "sequence",
+ "crops": [
+ "pr805-step1-edit-cancelled",
+ "pr805-step1-selection-kept",
+ "pr805-step2-next-edit-persisted"
+ ]
+ },
+ {
+ "id": "pr811-order",
+ "kind": "pair",
+ "crops": [
+ "pr811-before-order-row",
+ "pr811-after-order-row",
+ "pr811-before-order-inspector",
+ "pr811-after-order-inspector"
+ ]
+ },
+ {
+ "id": "pr811-restored",
+ "kind": "single",
+ "crops": [
+ "pr811-restored-notice"
+ ]
+ },
+ {
+ "id": "pr823-restored",
+ "kind": "single",
+ "crops": [
+ "pr823-restored-fix-with-ai"
+ ]
+ },
+ {
+ "id": "pr705-sequence",
+ "kind": "sequence",
+ "crops": [
+ "pr705-step1-dialog",
+ "pr705-step2-created-list",
+ "pr705-step2-created-canvas"
+ ]
+ }
+ ],
+ "videos": []
+}
diff --git a/reports/hyper-2026-10/media/orig/pr665-after.png b/reports/hyper-2026-10/media/orig/pr665-after.png
new file mode 100644
index 0000000..287a209
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr665-after.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr705-created.png b/reports/hyper-2026-10/media/orig/pr705-created.png
new file mode 100644
index 0000000..23166ed
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr705-created.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr705-dialog.png b/reports/hyper-2026-10/media/orig/pr705-dialog.png
new file mode 100644
index 0000000..8db9e5b
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr705-dialog.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr721-after-final.png b/reports/hyper-2026-10/media/orig/pr721-after-final.png
new file mode 100644
index 0000000..54ad7d8
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr721-after-final.png differ
diff --git a/reports/hyper-2026-10/images/explorer-no-folder-after.png b/reports/hyper-2026-10/media/orig/pr721-after-interim.png
similarity index 100%
rename from reports/hyper-2026-10/images/explorer-no-folder-after.png
rename to reports/hyper-2026-10/media/orig/pr721-after-interim.png
diff --git a/reports/hyper-2026-10/media/orig/pr721-after-link-clicked.png b/reports/hyper-2026-10/media/orig/pr721-after-link-clicked.png
new file mode 100644
index 0000000..e294350
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr721-after-link-clicked.png differ
diff --git a/reports/hyper-2026-10/images/explorer-no-folder-before.png b/reports/hyper-2026-10/media/orig/pr721-before.png
similarity index 100%
rename from reports/hyper-2026-10/images/explorer-no-folder-before.png
rename to reports/hyper-2026-10/media/orig/pr721-before.png
diff --git a/reports/hyper-2026-10/media/orig/pr738-after.png b/reports/hyper-2026-10/media/orig/pr738-after.png
new file mode 100644
index 0000000..22b59cb
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr738-after.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr794-after.png b/reports/hyper-2026-10/media/orig/pr794-after.png
new file mode 100644
index 0000000..0bb9fbc
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr794-after.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr794-before.png b/reports/hyper-2026-10/media/orig/pr794-before.png
new file mode 100644
index 0000000..852df40
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr794-before.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr805-edit-cancelled.png b/reports/hyper-2026-10/media/orig/pr805-edit-cancelled.png
new file mode 100644
index 0000000..c16fe41
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr805-edit-cancelled.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr805-next-edit.png b/reports/hyper-2026-10/media/orig/pr805-next-edit.png
new file mode 100644
index 0000000..d618b0d
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr805-next-edit.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr811-order-after.png b/reports/hyper-2026-10/media/orig/pr811-order-after.png
new file mode 100644
index 0000000..af37a9e
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr811-order-after.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr811-order-before.png b/reports/hyper-2026-10/media/orig/pr811-order-before.png
new file mode 100644
index 0000000..df4ac15
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr811-order-before.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr811-restored-toast.png b/reports/hyper-2026-10/media/orig/pr811-restored-toast.png
new file mode 100644
index 0000000..a08b7a1
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr811-restored-toast.png differ
diff --git a/reports/hyper-2026-10/media/orig/pr823-restored-toast.png b/reports/hyper-2026-10/media/orig/pr823-restored-toast.png
new file mode 100644
index 0000000..61801c5
Binary files /dev/null and b/reports/hyper-2026-10/media/orig/pr823-restored-toast.png differ
diff --git a/reports/hyper-2026-10/images/style-write-after.png b/reports/hyper-2026-10/media/orig/pr827-after.png
similarity index 100%
rename from reports/hyper-2026-10/images/style-write-after.png
rename to reports/hyper-2026-10/media/orig/pr827-after.png
diff --git a/reports/hyper-2026-10/images/style-write-before.png b/reports/hyper-2026-10/media/orig/pr827-before.png
similarity index 100%
rename from reports/hyper-2026-10/images/style-write-before.png
rename to reports/hyper-2026-10/media/orig/pr827-before.png
diff --git a/reports/hyper-2026-10/media/pr665-after-native-warning.png b/reports/hyper-2026-10/media/pr665-after-native-warning.png
new file mode 100644
index 0000000..dfff0a0
Binary files /dev/null and b/reports/hyper-2026-10/media/pr665-after-native-warning.png differ
diff --git a/reports/hyper-2026-10/media/pr705-step1-dialog.png b/reports/hyper-2026-10/media/pr705-step1-dialog.png
new file mode 100644
index 0000000..8e87429
Binary files /dev/null and b/reports/hyper-2026-10/media/pr705-step1-dialog.png differ
diff --git a/reports/hyper-2026-10/media/pr705-step2-created-canvas.png b/reports/hyper-2026-10/media/pr705-step2-created-canvas.png
new file mode 100644
index 0000000..ec91a1a
Binary files /dev/null and b/reports/hyper-2026-10/media/pr705-step2-created-canvas.png differ
diff --git a/reports/hyper-2026-10/media/pr705-step2-created-list.png b/reports/hyper-2026-10/media/pr705-step2-created-list.png
new file mode 100644
index 0000000..0a30a7a
Binary files /dev/null and b/reports/hyper-2026-10/media/pr705-step2-created-list.png differ
diff --git a/reports/hyper-2026-10/media/pr721-after-explorer.png b/reports/hyper-2026-10/media/pr721-after-explorer.png
new file mode 100644
index 0000000..b8a6732
Binary files /dev/null and b/reports/hyper-2026-10/media/pr721-after-explorer.png differ
diff --git a/reports/hyper-2026-10/media/pr721-after-interim-explorer.png b/reports/hyper-2026-10/media/pr721-after-interim-explorer.png
new file mode 100644
index 0000000..b8fce68
Binary files /dev/null and b/reports/hyper-2026-10/media/pr721-after-interim-explorer.png differ
diff --git a/reports/hyper-2026-10/media/pr721-before-explorer.png b/reports/hyper-2026-10/media/pr721-before-explorer.png
new file mode 100644
index 0000000..0265447
Binary files /dev/null and b/reports/hyper-2026-10/media/pr721-before-explorer.png differ
diff --git a/reports/hyper-2026-10/media/pr721-link-clicked-explorer.png b/reports/hyper-2026-10/media/pr721-link-clicked-explorer.png
new file mode 100644
index 0000000..a5f967c
Binary files /dev/null and b/reports/hyper-2026-10/media/pr721-link-clicked-explorer.png differ
diff --git a/reports/hyper-2026-10/media/pr738-after-canvas-selection.png b/reports/hyper-2026-10/media/pr738-after-canvas-selection.png
new file mode 100644
index 0000000..6f4296c
Binary files /dev/null and b/reports/hyper-2026-10/media/pr738-after-canvas-selection.png differ
diff --git a/reports/hyper-2026-10/media/pr738-after-inspector-warning.png b/reports/hyper-2026-10/media/pr738-after-inspector-warning.png
new file mode 100644
index 0000000..8da82ce
Binary files /dev/null and b/reports/hyper-2026-10/media/pr738-after-inspector-warning.png differ
diff --git a/reports/hyper-2026-10/media/pr794-after-canvas.png b/reports/hyper-2026-10/media/pr794-after-canvas.png
new file mode 100644
index 0000000..dc99fec
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-after-canvas.png differ
diff --git a/reports/hyper-2026-10/media/pr794-after-notice.png b/reports/hyper-2026-10/media/pr794-after-notice.png
new file mode 100644
index 0000000..65d9d21
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-after-notice.png differ
diff --git a/reports/hyper-2026-10/media/pr794-after-tab.png b/reports/hyper-2026-10/media/pr794-after-tab.png
new file mode 100644
index 0000000..80aefa4
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-after-tab.png differ
diff --git a/reports/hyper-2026-10/media/pr794-after-tree.png b/reports/hyper-2026-10/media/pr794-after-tree.png
new file mode 100644
index 0000000..5841d1d
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-after-tree.png differ
diff --git a/reports/hyper-2026-10/media/pr794-before-canvas.png b/reports/hyper-2026-10/media/pr794-before-canvas.png
new file mode 100644
index 0000000..2a8a1dd
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-before-canvas.png differ
diff --git a/reports/hyper-2026-10/media/pr794-before-notice.png b/reports/hyper-2026-10/media/pr794-before-notice.png
new file mode 100644
index 0000000..a8127f6
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-before-notice.png differ
diff --git a/reports/hyper-2026-10/media/pr794-before-tab.png b/reports/hyper-2026-10/media/pr794-before-tab.png
new file mode 100644
index 0000000..f4970bf
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-before-tab.png differ
diff --git a/reports/hyper-2026-10/media/pr794-before-tree.png b/reports/hyper-2026-10/media/pr794-before-tree.png
new file mode 100644
index 0000000..37bf35f
Binary files /dev/null and b/reports/hyper-2026-10/media/pr794-before-tree.png differ
diff --git a/reports/hyper-2026-10/media/pr805-step1-edit-cancelled.png b/reports/hyper-2026-10/media/pr805-step1-edit-cancelled.png
new file mode 100644
index 0000000..c3563e8
Binary files /dev/null and b/reports/hyper-2026-10/media/pr805-step1-edit-cancelled.png differ
diff --git a/reports/hyper-2026-10/media/pr805-step1-selection-kept.png b/reports/hyper-2026-10/media/pr805-step1-selection-kept.png
new file mode 100644
index 0000000..887bbc6
Binary files /dev/null and b/reports/hyper-2026-10/media/pr805-step1-selection-kept.png differ
diff --git a/reports/hyper-2026-10/media/pr805-step2-next-edit-persisted.png b/reports/hyper-2026-10/media/pr805-step2-next-edit-persisted.png
new file mode 100644
index 0000000..196291b
Binary files /dev/null and b/reports/hyper-2026-10/media/pr805-step2-next-edit-persisted.png differ
diff --git a/reports/hyper-2026-10/media/pr811-after-order-inspector.png b/reports/hyper-2026-10/media/pr811-after-order-inspector.png
new file mode 100644
index 0000000..96e0d43
Binary files /dev/null and b/reports/hyper-2026-10/media/pr811-after-order-inspector.png differ
diff --git a/reports/hyper-2026-10/media/pr811-after-order-row.png b/reports/hyper-2026-10/media/pr811-after-order-row.png
new file mode 100644
index 0000000..c3f2314
Binary files /dev/null and b/reports/hyper-2026-10/media/pr811-after-order-row.png differ
diff --git a/reports/hyper-2026-10/media/pr811-before-order-inspector.png b/reports/hyper-2026-10/media/pr811-before-order-inspector.png
new file mode 100644
index 0000000..cb14905
Binary files /dev/null and b/reports/hyper-2026-10/media/pr811-before-order-inspector.png differ
diff --git a/reports/hyper-2026-10/media/pr811-before-order-row.png b/reports/hyper-2026-10/media/pr811-before-order-row.png
new file mode 100644
index 0000000..a97551b
Binary files /dev/null and b/reports/hyper-2026-10/media/pr811-before-order-row.png differ
diff --git a/reports/hyper-2026-10/media/pr811-restored-notice.png b/reports/hyper-2026-10/media/pr811-restored-notice.png
new file mode 100644
index 0000000..5d27333
Binary files /dev/null and b/reports/hyper-2026-10/media/pr811-restored-notice.png differ
diff --git a/reports/hyper-2026-10/media/pr823-restored-fix-with-ai.png b/reports/hyper-2026-10/media/pr823-restored-fix-with-ai.png
new file mode 100644
index 0000000..df876a7
Binary files /dev/null and b/reports/hyper-2026-10/media/pr823-restored-fix-with-ai.png differ
diff --git a/reports/hyper-2026-10/media/pr827-after-canvas.png b/reports/hyper-2026-10/media/pr827-after-canvas.png
new file mode 100644
index 0000000..b7b91d9
Binary files /dev/null and b/reports/hyper-2026-10/media/pr827-after-canvas.png differ
diff --git a/reports/hyper-2026-10/media/pr827-after-inspector.png b/reports/hyper-2026-10/media/pr827-after-inspector.png
new file mode 100644
index 0000000..6ba5a29
Binary files /dev/null and b/reports/hyper-2026-10/media/pr827-after-inspector.png differ
diff --git a/reports/hyper-2026-10/media/pr827-before-canvas.png b/reports/hyper-2026-10/media/pr827-before-canvas.png
new file mode 100644
index 0000000..2846b0c
Binary files /dev/null and b/reports/hyper-2026-10/media/pr827-before-canvas.png differ
diff --git a/reports/hyper-2026-10/media/pr827-before-inspector.png b/reports/hyper-2026-10/media/pr827-before-inspector.png
new file mode 100644
index 0000000..38b062b
Binary files /dev/null and b/reports/hyper-2026-10/media/pr827-before-inspector.png differ
diff --git a/reports/shared/media-proof-20261106.css b/reports/shared/media-proof-20261106.css
new file mode 100644
index 0000000..ce5897a
--- /dev/null
+++ b/reports/shared/media-proof-20261106.css
@@ -0,0 +1,301 @@
+/* Annotated screenshots, original requests and recordings. Loaded after product-report-20261106.css. The two October stylesheets stay byte-identical. */
+.shot-group,
+.clip-group {
+ margin: 16px 0;
+ padding: 14px 16px;
+ background: var(--card);
+ border: 1px solid var(--border);
+ border-radius: 10px;
+}
+.shot-group > h4,
+.clip-group > h4 {
+ margin: 0 0 6px;
+ font-size: 1rem;
+ color: var(--text);
+}
+.shot-group .shot-status {
+ margin: 0 0 10px;
+ font-size: 0.85rem;
+ color: var(--text-dim);
+}
+.shot-group .shot-badge {
+ display: inline-block;
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ font-size: 0.75rem;
+ padding: 4px 8px;
+ margin: 0 6px 0 0;
+}
+.shot-grid {
+ display: grid;
+ gap: 14px;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ align-items: start;
+}
+.shot-grid-single,
+.shot-grid-sequence {
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+}
+.shot-single .shot-grid-single {
+ grid-template-columns: repeat(auto-fit, minmax(280px, 520px));
+}
+.layout-seq-dialog .shot-grid-sequence {
+ grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
+ grid-template-rows: auto 1fr;
+}
+.layout-seq-dialog .shot-grid-sequence > .shot:first-child {
+ grid-row: span 2;
+}
+.shot {
+ margin: 0;
+ min-width: 0;
+}
+.shot-link {
+ display: block;
+ line-height: 0;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ overflow: hidden;
+ background: #000;
+ cursor: zoom-in;
+}
+.shot-link:focus-visible {
+ outline: 3px solid var(--accent1);
+ outline-offset: 2px;
+}
+.shot img {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+.shot figcaption {
+ margin-top: 8px;
+ font-size: 0.85rem;
+ line-height: 1.5;
+ color: var(--text-dim);
+ overflow-wrap: anywhere;
+}
+.chip {
+ display: inline-block;
+ font-size: 0.72rem;
+ font-weight: 700;
+ line-height: 1.5;
+ padding: 0 8px;
+ margin-right: 4px;
+ border: 1px solid var(--border);
+ border-radius: 10px;
+ color: var(--text);
+ white-space: nowrap;
+}
+.chip-before {
+ color: var(--red);
+ border-color: rgba(248, 81, 73, 0.6);
+}
+.chip-after {
+ color: var(--green);
+ border-color: rgba(63, 185, 80, 0.6);
+}
+.chip-step {
+ color: var(--accent1);
+ border-color: rgba(88, 166, 255, 0.6);
+}
+.chip-interim {
+ color: var(--yellow);
+ border-color: rgba(210, 153, 34, 0.6);
+}
+.chip-context {
+ color: var(--text-dim);
+}
+.chip-clip {
+ color: var(--accent2);
+ border-color: rgba(188, 140, 255, 0.6);
+ white-space: normal;
+}
+.original-link {
+ display: inline-block;
+ white-space: nowrap;
+}
+ol.marks {
+ list-style: none;
+ margin: 8px 0 0;
+ padding: 0;
+ counter-reset: mark;
+}
+ol.marks li {
+ counter-increment: mark;
+ position: relative;
+ margin: 4px 0;
+ padding-left: 28px;
+ color: var(--text);
+}
+ol.marks li::before {
+ content: counter(mark);
+ position: absolute;
+ left: 0;
+ top: 1px;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ background: #ffd400;
+ border: 2px solid #000;
+ color: #000;
+ font-size: 0.72rem;
+ font-weight: 700;
+ line-height: 16px;
+ text-align: center;
+}
+.more-shots {
+ margin-top: 14px;
+}
+.more-shots > summary {
+ cursor: pointer;
+ color: var(--text);
+ font-weight: 600;
+ margin-bottom: 10px;
+}
+.shot-source {
+ margin: 12px 0 0;
+ font-size: 0.82rem;
+ color: var(--text-dim);
+ line-height: 1.5;
+}
+.owner-requests {
+ margin: 14px 0;
+}
+.owner-request {
+ margin: 10px 0;
+ padding: 12px 16px;
+ background: rgba(88, 166, 255, 0.05);
+ border: 1px solid rgba(88, 166, 255, 0.18);
+ border-left: 4px solid var(--accent1);
+ border-radius: 8px;
+}
+.owner-request > h4,
+.owner-request > h5 {
+ margin: 0 0 8px;
+ font-size: 0.82rem;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--text-dim);
+}
+.request-cols {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px 18px;
+}
+.request-col {
+ min-width: 0;
+}
+.request-label {
+ margin: 0 0 4px;
+ font-size: 0.75rem;
+ font-weight: 700;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+ color: var(--accent1);
+}
+.owner-request blockquote {
+ margin: 0;
+ padding-left: 12px;
+ border-left: 3px solid rgba(88, 166, 255, 0.5);
+ color: var(--text);
+ font-size: 0.98rem;
+ line-height: 1.55;
+ overflow-wrap: anywhere;
+}
+.owner-request .request-source,
+.owner-request .request-link,
+.owner-request .request-note {
+ margin: 8px 0 0;
+ max-width: none;
+ font-size: 0.82rem;
+ line-height: 1.5;
+ color: var(--text-dim);
+ overflow-wrap: anywhere;
+}
+.owner-request .request-source strong,
+.owner-request .request-link strong {
+ color: var(--text);
+}
+.owner-request code {
+ font-size: 0.78rem;
+ word-break: break-all;
+}
+.clip {
+ margin: 14px 0;
+}
+.clip-narrow {
+ max-width: 640px;
+}
+.clip video {
+ display: block;
+ width: 100%;
+ max-width: 100%;
+ height: auto;
+ max-height: 80vh;
+ background: #000;
+ border-radius: 8px;
+}
+.clip figcaption {
+ margin-top: 8px;
+ font-size: 0.85rem;
+ line-height: 1.5;
+ color: var(--text-dim);
+ overflow-wrap: anywhere;
+}
+.clip-pair {
+ display: grid;
+ grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
+ gap: 14px;
+ align-items: start;
+}
+.clip-meta {
+ margin: 8px 0 0;
+ display: grid;
+ grid-template-columns: max-content minmax(0, 1fr);
+ gap: 4px 12px;
+}
+.clip-meta dt {
+ color: var(--text);
+ font-weight: 600;
+}
+.clip-meta dd {
+ margin: 0;
+}
+.clip-meta code {
+ font-size: 0.75rem;
+ word-break: break-all;
+}
+.visual-table td:first-child {
+ min-width: 12em;
+}
+@media (max-width: 650px) {
+ .shot-group,
+ .clip-group {
+ padding: 12px 10px;
+ }
+ .shot-grid,
+ .shot-grid-single,
+ .shot-grid-sequence,
+ .layout-seq-dialog .shot-grid-sequence,
+ .shot-single .shot-grid-single,
+ .request-cols,
+ .clip-pair {
+ grid-template-columns: minmax(0, 1fr);
+ }
+ .layout-seq-dialog .shot-grid-sequence > .shot:first-child {
+ grid-row: auto;
+ }
+ .layout-seq-dialog .shot-grid-sequence > .shot:first-child .shot-link {
+ max-width: 320px;
+ }
+ .owner-request {
+ padding: 10px 12px;
+ }
+ .clip-meta {
+ grid-template-columns: minmax(0, 1fr);
+ }
+ .clip-meta dt {
+ margin-top: 6px;
+ }
+}
diff --git a/reports/shared/product-report-20261106.css b/reports/shared/product-report-20261106.css
index 96e6b46..a2d826d 100644
--- a/reports/shared/product-report-20261106.css
+++ b/reports/shared/product-report-20261106.css
@@ -10,8 +10,7 @@
.outcomes-index .family-nav li {
margin: 0;
}
-.report-section > .outcomes,
-.report-section > .figure-pair {
+.report-section > .outcomes {
margin-top: 18px;
}
.report-section h3 {
@@ -202,50 +201,10 @@ ul.plain blockquote {
color: var(--text);
overflow-wrap: anywhere;
}
-.figure-pair {
- background: var(--card);
- border: 1px solid var(--border);
- border-left: 4px solid var(--border);
- border-radius: 10px;
- padding: 20px 24px;
- margin: 18px 0;
-}
-.figure-pair.kind-merged {
- border-left-color: var(--green);
-}
-.figure-pair.kind-pilot {
- border-left-color: var(--orange);
-}
-.figure-pair.kind-implemented-branch {
- border-left-color: var(--yellow);
-}
-.figure-pair .refs {
- margin-top: 12px;
-}
@media (max-width: 650px) {
- .figure-pair {
- padding: 16px 14px;
- }
figure.requirement {
padding: 10px 12px;
}
- /* Phone crops magnify one region of a real capture. Each offset is tuned to its PNG:
- crop-explorer shows the Explorer panel of the 2400x1536 capture (top-left 30% x 26%);
- crop-inspector shows the inspector width field of the 1440x835 capture (right 28%).
- Re-tune both if an image is replaced. */
- .crop.crop-explorer img {
- left: 0;
- top: -11.4%;
- width: 333%;
- }
- .crop.crop-inspector {
- aspect-ratio: 5 / 4;
- }
- .crop.crop-inspector img {
- left: -257.1%;
- top: -72.5%;
- width: 357.1%;
- }
.outcome details.evidence ul {
margin-left: 16px;
}
diff --git a/reports/state-lib-2026-10/index.html b/reports/state-lib-2026-10/index.html
index ce5c24d..e34a050 100644
--- a/reports/state-lib-2026-10/index.html
+++ b/reports/state-lib-2026-10/index.html
@@ -7,10 +7,11 @@
+
-
StateLib, in packages/state-lib of the HyperIDE repository, is a synchronous state Runtime with a Rust compiler for application entries and a documentation site with live examples. In October Ultra / Mextner added async state and immutable records to the Runtime, stopped stale async work from publishing, compiled whole entries with rollback, removed a document copy from cold import, and added live examples that compile in the browser.
The numbers on this page are historical local measurements on benchmark fixtures, each tied to the pull request that recorded it. They are not claims about the whole editor, which does not import state-lib on main. The Style Editor pilot is described in the HyperIDE report.
4.15 → 0.006 ms
point edit on a 10,000-item benchmark fixture, Node median (#802)
63 → 1 ms
completion handler, 1,200 visible rows, Chromium median (#832)
15.6 → 4.7 MB
Chromium retained heap, 10,000 rows with 1,200 visible (#832)
What we delivered
One tested async toolkit instead of per-framework glue
A framework-neutral async toolkit now sits on the Runtime, with an async resource, matchAsyncState and a conformance suite. Production and testing entry points are separate, and an import-boundary test checks the split. One implementation means an async-state bug is fixed once instead of once per framework.
Technical detail
Package @ultrabricks/state-toolkit-async@0.0.0-pilot.0 under packages/state-lib/toolkit, with a peer on @ultrabricks/state-lib@0.1.0-pilot.1. The . and ./testing exports are checked by import-boundary.test.ts and package-contract.test.ts.
Tests: async-conformance.test.ts, async-ownership.test.ts, async-resource.test.ts, async-thenable-once.test.ts and match-async-state.test.ts.
Validated immutable records on the existing Runtime
createRecord adds validated, deeply frozen records on the Runtime for small plain objects such as editor workspace preferences. Callers cannot change a committed snapshot, and a nested update that re-enters the same record now throws instead of letting a stale draft overwrite it. It reuses the Runtime’s transactions, so there is no second state engine to maintain.
Technical detail
A regression test written first reproduced the re-entrancy bug. The error is STATE_RECORD_REENTRANT, thrown before the inner mutation, and async recipes are rejected.
record.test.ts runs under a strict tsconfig.record.json; the pull request records 75 tests passing across core, regression, follow-up and adapter cases.
createOperationScope gives each lane of async work a context generation. Once a lane is replaced, its context changes or it is disposed, the old write or feedback cannot publish, and an old finish or cancel cannot release a newer lease. It is library code; the pull request does not turn it on in the inspector.
Technical detail
Independent lanes, synchronous commit and notify gates, callback binding checked at invocation, and bounded metadata-only diagnostics.
Tests: operation-scope.test.ts, operation-scope-async.test.ts and a type fixture. A real-Chromium check runs the emitted package pair through nine scenarios, and with the stale-publication guard removed, five of seven checks fail.
Package export fails closed and records provenance
The package exporter assembles in an owner-only staging directory on the destination filesystem and publishes by rename. It refuses to overwrite existing files, directories or dangling symlinks and refuses an output directory inside the source trees. Each export records source and output hashes and tool versions, so a package can be traced to its source.
Technical detail
export-package.mjs keeps the original cause on errors and reports both failures if export and cleanup fail. export-filesystem.test.ts covers it, and #826 added cases. The pull request states the limits: the publish is not atomic no-replace and not crash-durable.
Generated commands and complete entries with rollback
The compiler generates immutable commands for declared edits such as assign, toggle, row assign and move, so on the benchmark fixture those edits no longer copy the whole document. state build compiles complete fixed application entries and publishes each output with rollback, and size budgets reject an oversized entry and keep the previous output. The figures below carry their fixtures and comparators; they are not a general speed or size claim.
Technical detail
Native Rust compiler: packages/state-lib/compiler/src/command.rs and lib.rs. Median improved in all 42 Node measurements; 26 of 42 also pass the stricter max-versus-min cell test (#802).
One ownership-aware publication and history module runs on a single dedicated Runtime. Entry budgets are maxRawBytes, maxGzipBytes and maxBrotliBytes plus a relative-growth limit (#802).
Fingerprint walks went from three to two and path resolution is cached within a build. A lock-cleanup failure after a successful install is only a warning (#832). A hole that let an alias into authored src/node_modules bypass the browser-root check was closed, with nine regression tests; the pull request says this is not a sandbox for third-party code.
An optional immutable-row adapter updates completion on existing rows in the board view and falls back to the full renderer for unsupported changes (#832). On the small fixtures the same pull request records about 32 to 38 KB more retained heap, and an entry of 42,166 gzip bytes against 37,132 for the equally optimized original.
importJsonField certifies a JSON field while importing it, and replaceImported adopts that exact root. A cold load skips one whole-document structuredClone, while the normal replace() still copies and behaves as before.
Technical detail
replaceImported throws TypeError for anything not produced by importJsonField, so the shortcut cannot be used with an uncertified value. Certification, freezing, history reset and the save payload contract are unchanged.
owned-import.test.ts was written red first and has 11 passing tests. The pull request records whole-call structuredClone uses during load going from 3 to 2 and makes no timing claim.
Live examples, a keyed-row patch boundary and correct docs
The documentation site has two live examples, /repl/board-commands and /repl/issue-triage. Visitors edit multi-file source, compile it with the real Rust compiler in WebAssembly, and see generated commands, readable and minified output, sizes, live state, Undo and Redo, and browser benchmarks. A checked keyed-row patch boundary lets an app update real text edits without rebuilding the list, and the generated API docs keep ordinary member names.
Technical detail
Edited code runs in a disposable worker inside an opaque-origin, network-denied frame. Cancellation, latest-request authority, timeout and keeping the last good build after a failed build are tested (#809).
patchKeyedRows locates every target before the first row write and rejects a null or invalid plan without applying rows; the pull request documents that a throwing callback leaves a view to rebuild (#833). 14 planner and boundary tests pass.
apiSymbolName() used to throw on a real member named like __@iterator@999 and abort API-doc generation. It now accepts such a name only when a declaration’s string literal is exactly that name, and the regenerated docs are unchanged at 119 entries (#807).
Diagram captions no longer show quoted attribute text, and docs-site readiness waits for exactly three lazy editors. SVG comparison in the gallery tolerates only narrowly parsed coordinate roundoff (#810).
Historical local measurements, recorded in the pull requests named in the last column; the first column gives the fixture. #802 improves the median in all 42 Node measurements and passes its stricter cell test in 26 of 42. #832 records about 32 to 38 KB more retained heap on small fixtures.
Measure
Before
After
Source
Point edit, 10,000-item benchmark fixture, Node median
Diagram caption in the StateLib documentation site, captured for #810 on 14 October. The same fixture is shown before and after the fix. Tap an image to open it full size.
Before: b"> leaks into the caption.After: only the intended label is rendered.
Original browser captures from 14 October, retained with #810.
Proof
Hosted CI runs for the final commit of each pull request, or for the merge commit where noted.
Work by Ultra / Mextner (GitHub alex-mextner, Alex Ultra). Pull requests merged 1 to 31 October 2026 are credited by commit authorship. Status checked 6 November. Source links may require repository access.
Attribution: every commit in these pull requests is by Ultra. #802, #809 and #833 were opened from the smoothbricker account and merged by alex-mextner.
All merged pull requests counted here are listed with their authors and merge commits in the source record.
StateLib, in packages/state-lib of the HyperIDE repository, is a synchronous state Runtime with a Rust compiler for application entries and a documentation site with live examples. In October Ultra / Mextner added async state and immutable records to the Runtime, stopped stale async work from publishing, compiled whole entries with rollback, removed a document copy from cold import, and added live examples that compile in the browser.
The numbers on this page are historical local measurements on benchmark fixtures, each tied to the pull request that recorded it. They are not claims about the whole editor, which does not import state-lib on main. The Style Editor pilot is described in the HyperIDE report.
4.15 → 0.006 ms
point edit on a 10,000-item benchmark fixture, Node median (#802)
63 → 1 ms
completion handler, 1,200 visible rows, Chromium median (#832)
15.6 → 4.7 MB
Chromium retained heap, 10,000 rows with 1,200 visible (#832)
Owner requests behind the library work
These are the owner’s own messages about where the library should live and what it should do. They state direction and wishes. What shipped is in the results below, and this report makes no claim that the Style Editor adopted the library.
Owner request
Original request (RU)
Хм кажется state-lib живет в GitHub hyper-saas Давай вынесем оттуда И консолидируй все ворктри по state-lib, доделай и влей все через gh ship
English translation
Hm, it seems state-lib lives in the hyper-saas GitHub repository. Let's move it out of there. And consolidate all the worktrees on state-lib, finish them and merge everything through gh ship.
Source. Ultra / Mextner · Telegram, private chat between Ultra and the assistant bot · tg#23556, bot-side message id 23556 · 22 October 2026, 17:57 UTC. Sender: incoming Telegram message from the owner's account (not the assistant, not another person). Full-message SHA-256 eba04f1a240a15249c1810017ef295947c2d60a0eeb31948db135633622e90ff.
Inferred link. A request about the library's location and its open work. This report describes code in packages/state-lib of the HyperIDE repository and makes no claim that the library was moved out.
Owner request
Original request (RU)
Ага над hsw продолжай параллельно работать используй пока state-lib как приватную зависимость или вендоренную
English translation
Aha, keep working on hsw in parallel, and use state-lib for now as a private dependency or vendored.
Source. Ultra / Mextner · Telegram, private chat between Ultra and the assistant bot · tg#23559, bot-side message id 23559 · 22 October 2026, 17:59 UTC. Sender: incoming Telegram message from the owner's account (not the assistant, not another person). Full-message SHA-256 5ae64db0c5f19e8742df805c23c4e09f762e86b0c6c17a9ec373cf7058241a13.
Inferred link. The follow-up two minutes later. The same limit applies, because it is a request only and what shipped is stated in the results.
Owner request
Original request (RU)
еще мы с денни обсудили что хотим в нашем state lint запретить useEffect и подобные паттерны и сделать продвинутые миграции, надо изучить типичные паттерны и как они будут декларативно решаться в сторе
English translation
Also, Denni and I discussed that we want to forbid useEffect and similar patterns in our state lint and to build advanced migrations; we need to study the typical patterns and how they will be solved declaratively in the store.
Source. Ultra / Mextner · Ultra's Claude Code prompt history, project hyperide · no message id · 4 September 2026, 13:56 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 699195e29c111b3c883bdfe53c5075ca59c1eb35ac56af7d756556d0b2458ed3.
Inferred link. A statement of library goals. The report does not claim that a lint rule or migrations shipped.
4 September 2026, before the October period of this report; kept because it states the library's goals.
Owner request (excerpt)
Original request (RU)
еще интересно как мы реализуем описанную ранее идею разделения разных смысловых регионов стейта: busines, automations, ui state, и чтото еще вроде стоит выделить
English translation
Also, I wonder how we will implement the idea described earlier of separating different semantic regions of state: business, automations, ui state, and maybe something else should be separated.
Source. Ultra / Mextner · Ultra's Claude Code prompt history, project hyperide · no message id · 4 September 2026, 12:14 UTC. Sender: the owner's own prompt history; it has no per-message author field, so attribution is by account. Full-message SHA-256 d3afe37f08950cf6323fbb3b27e65f896a9652f41f6ca2770cea5fd5fc5a3908.
Inferred link. A statement of library goals; no claim that this separation shipped.
4 September 2026, before the October period of this report; kept because it states the library's goals. Only the first sentence of a longer multi-paragraph message is quoted.
What we delivered
One tested async toolkit instead of per-framework glue
A framework-neutral async toolkit now sits on the Runtime, with an async resource, matchAsyncState and a conformance suite. Production and testing entry points are separate, and an import-boundary test checks the split. One implementation means an async-state bug is fixed once instead of once per framework.
Technical detail
Package @ultrabricks/state-toolkit-async@0.0.0-pilot.0 under packages/state-lib/toolkit, with a peer on @ultrabricks/state-lib@0.1.0-pilot.1. The . and ./testing exports are checked by import-boundary.test.ts and package-contract.test.ts.
Tests: async-conformance.test.ts, async-ownership.test.ts, async-resource.test.ts, async-thenable-once.test.ts and match-async-state.test.ts.
Validated immutable records on the existing Runtime
createRecord adds validated, deeply frozen records on the Runtime for small plain objects such as editor workspace preferences. Callers cannot change a committed snapshot, and a nested update that re-enters the same record now throws instead of letting a stale draft overwrite it. It reuses the Runtime’s transactions, so there is no second state engine to maintain.
Technical detail
A regression test written first reproduced the re-entrancy bug. The error is STATE_RECORD_REENTRANT, thrown before the inner mutation, and async recipes are rejected.
record.test.ts runs under a strict tsconfig.record.json; the pull request records 75 tests passing across core, regression, follow-up and adapter cases.
createOperationScope gives each lane of async work a context generation. Once a lane is replaced, its context changes or it is disposed, the old write or feedback cannot publish, and an old finish or cancel cannot release a newer lease. It is library code; the pull request does not turn it on in the inspector.
Technical detail
Independent lanes, synchronous commit and notify gates, callback binding checked at invocation, and bounded metadata-only diagnostics.
Tests: operation-scope.test.ts, operation-scope-async.test.ts and a type fixture. A real-Chromium check runs the emitted package pair through nine scenarios, and with the stale-publication guard removed, five of seven checks fail.
Package export fails closed and records provenance
The package exporter assembles in an owner-only staging directory on the destination filesystem and publishes by rename. It refuses to overwrite existing files, directories or dangling symlinks and refuses an output directory inside the source trees. Each export records source and output hashes and tool versions, so a package can be traced to its source.
Technical detail
export-package.mjs keeps the original cause on errors and reports both failures if export and cleanup fail. export-filesystem.test.ts covers it, and #826 added cases. The pull request states the limits: the publish is not atomic no-replace and not crash-durable.
Generated commands and complete entries with rollback
The compiler generates immutable commands for declared edits such as assign, toggle, row assign and move, so on the benchmark fixture those edits no longer copy the whole document. state build compiles complete fixed application entries and publishes each output with rollback, and size budgets reject an oversized entry and keep the previous output. The figures below carry their fixtures and comparators; they are not a general speed or size claim.
Technical detail
Native Rust compiler: packages/state-lib/compiler/src/command.rs and lib.rs. Median improved in all 42 Node measurements; 26 of 42 also pass the stricter max-versus-min cell test (#802).
One ownership-aware publication and history module runs on a single dedicated Runtime. Entry budgets are maxRawBytes, maxGzipBytes and maxBrotliBytes plus a relative-growth limit (#802).
Fingerprint walks went from three to two and path resolution is cached within a build. A lock-cleanup failure after a successful install is only a warning (#832). A hole that let an alias into authored src/node_modules bypass the browser-root check was closed, with nine regression tests; the pull request says this is not a sandbox for third-party code.
An optional immutable-row adapter updates completion on existing rows in the board view and falls back to the full renderer for unsupported changes (#832). On the small fixtures the same pull request records about 32 to 38 KB more retained heap, and an entry of 42,166 gzip bytes against 37,132 for the equally optimized original.
importJsonField certifies a JSON field while importing it, and replaceImported adopts that exact root. A cold load skips one whole-document structuredClone, while the normal replace() still copies and behaves as before.
Technical detail
replaceImported throws TypeError for anything not produced by importJsonField, so the shortcut cannot be used with an uncertified value. Certification, freezing, history reset and the save payload contract are unchanged.
owned-import.test.ts was written red first and has 11 passing tests. The pull request records whole-call structuredClone uses during load going from 3 to 2 and makes no timing claim.
Live examples, a keyed-row patch boundary and correct docs
The documentation site has two live examples, /repl/board-commands and /repl/issue-triage. Visitors edit multi-file source, compile it with the real Rust compiler in WebAssembly, and see generated commands, readable and minified output, sizes, live state, Undo and Redo, and browser benchmarks. A checked keyed-row patch boundary lets an app update real text edits without rebuilding the list, and the generated API docs keep ordinary member names.
Diagram caption in the docs site
Merged, pull request #810 Before and after: same window, same region, same scale.
StateLib documentation site, captured for #810 on 14 October 2026 from the same fixture before and after the fix. Both crops cut the same region.
Technical detail
Edited code runs in a disposable worker inside an opaque-origin, network-denied frame. Cancellation, latest-request authority, timeout and keeping the last good build after a failed build are tested (#809).
patchKeyedRows locates every target before the first row write and rejects a null or invalid plan without applying rows; the pull request documents that a throwing callback leaves a view to rebuild (#833). 14 planner and boundary tests pass.
apiSymbolName() used to throw on a real member named like __@iterator@999 and abort API-doc generation. It now accepts such a name only when a declaration’s string literal is exactly that name, and the regenerated docs are unchanged at 119 entries (#807).
Diagram captions no longer show quoted attribute text, and docs-site readiness waits for exactly three lazy editors. SVG comparison in the gallery tolerates only narrowly parsed coordinate roundoff (#810).
Historical local measurements, recorded in the pull requests named in the last column; the first column gives the fixture. #802 improves the median in all 42 Node measurements and passes its stricter cell test in 26 of 42. #832 records about 32 to 38 KB more retained heap on small fixtures.
Owner request (excerpt)
Original request (RU)
Надо сравнивать с ней и без нее по разным критериям более детально
English translation
We need to compare with and without it in more detail, using different criteria.
Source. Ultra / Mextner · Ultra's archived ChatGPT conversation about StateLib and the Style Editor · no message id recovered · 16 October 2026, 13:33 UTC. Sender: user message retrieved from the archived conversation and re-read against it on 7 November 2026; no message link was recovered. Full-message SHA-256 8a4cf35e61141626bd5cf3164a66def49f813d75e1f7e5df0bedc366b0988296.
Inferred link. The request behind the benchmark comparisons. It asks for measurement; the figures below are fixture measurements, not an editor speedup.
The rest of the message, including its first sentences and the request to write benchmark tests, is not quoted. The full message was re-read from the archived conversation on 7 November 2026; an earlier retrieved copy had added full stops, so its hash is not used.
Owner request
Original request (RU)
Добро погнали. А также желательно чтобы оно давала и в цифрах более хорошей результаты работала быстрее бандл бы весил меньше
English translation
All right, let's go. Also it would be good for it to give better results in numbers: to run faster and for the bundle to weigh less.
Source. Ultra / Mextner · Ultra's archived ChatGPT conversation about StateLib and the Style Editor · no message id recovered · 16 October 2026, 16:32 UTC. Sender: user message retrieved from the archived conversation and re-read against it on 7 November 2026; no message link was recovered. Full-message SHA-256 2c95bb365d37e75dbe0d3a50c6459febef805e388034a4bd7ddaeb9a5ecb8c59.
Inferred link. Same conversation. It states the wish for faster runs and a smaller bundle; the numbers below show what was measured, on fixtures.
Measure
Before
After
Source
Point edit, 10,000-item benchmark fixture, Node median
Work by Ultra / Mextner (GitHub alex-mextner, Alex Ultra). Pull requests merged 1 to 31 October 2026 are credited by commit authorship. Status checked 6 November. Source links may require repository access.
Attribution: every commit in these pull requests is by Ultra. #802, #809 and #833 were opened from the smoothbricker account and merged by alex-mextner.
All merged pull requests counted here are listed with their authors and merge commits in the source record.
diff --git a/reports/state-lib-2026-10/media-evidence.json b/reports/state-lib-2026-10/media-evidence.json
new file mode 100644
index 0000000..090e961
--- /dev/null
+++ b/reports/state-lib-2026-10/media-evidence.json
@@ -0,0 +1,270 @@
+{
+ "report": "statelib",
+ "generatedAt": "2026-11-06",
+ "note": "Public-safe subset: hashes, crop coordinates and source labels. Private chat text outside the quoted excerpts, raw message dumps, local file paths and protected originals are not included.",
+ "originalRequests": [
+ {
+ "id": "req-state-lib-extract",
+ "outcome": "statelib-requests",
+ "report": "statelib",
+ "sourceRecord": "F1",
+ "language": "ru",
+ "ru": "Хм кажется state-lib живет в GitHub hyper-saas \nДавай вынесем оттуда \nИ консолидируй все ворктри по state-lib, доделай и влей все через gh ship",
+ "en": "Hm, it seems state-lib lives in the hyper-saas GitHub repository. Let's move it out of there. And consolidate all the worktrees on state-lib, finish them and merge everything through gh ship.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "eba04f1a240a15249c1810017ef295947c2d60a0eeb31948db135633622e90ff",
+ "messageSha256": "eba04f1a240a15249c1810017ef295947c2d60a0eeb31948db135633622e90ff",
+ "dateUtc": "2026-10-22T17:57:09Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#23556, bot-side message id 23556",
+ "ids": {
+ "tg": "tg#23556",
+ "botMessageId": 23556
+ },
+ "relationKind": "inferred",
+ "relation": "A request about the library's location and its open work. This report describes code in packages/state-lib of the HyperIDE repository and makes no claim that the library was moved out.",
+ "windowNote": "22 October 2026",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-state-lib-private-dep",
+ "outcome": "statelib-requests",
+ "report": "statelib",
+ "sourceRecord": "F2",
+ "language": "ru",
+ "ru": "Ага над hsw продолжай параллельно работать используй пока state-lib как приватную зависимость или вендоренную",
+ "en": "Aha, keep working on hsw in parallel, and use state-lib for now as a private dependency or vendored.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "5ae64db0c5f19e8742df805c23c4e09f762e86b0c6c17a9ec373cf7058241a13",
+ "messageSha256": "5ae64db0c5f19e8742df805c23c4e09f762e86b0c6c17a9ec373cf7058241a13",
+ "dateUtc": "2026-10-22T17:59:42Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Telegram, private chat between Ultra and the assistant bot",
+ "senderBasis": "incoming Telegram message from the owner's account (not the assistant, not another person)",
+ "messageIdentity": "tg#23559, bot-side message id 23559",
+ "ids": {
+ "tg": "tg#23559",
+ "botMessageId": 23559
+ },
+ "relationKind": "inferred",
+ "relation": "The follow-up two minutes later. The same limit applies, because it is a request only and what shipped is stated in the results.",
+ "windowNote": "22 October 2026",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-state-lib-no-useeffect",
+ "outcome": "statelib-requests",
+ "report": "statelib",
+ "sourceRecord": "F3",
+ "language": "ru",
+ "ru": "еще мы с денни обсудили что хотим в нашем state lint запретить useEffect и подобные паттерны и сделать продвинутые миграции, надо изучить типичные паттерны и как они будут декларативно решаться в сторе",
+ "en": "Also, Denni and I discussed that we want to forbid useEffect and similar patterns in our state lint and to build advanced migrations; we need to study the typical patterns and how they will be solved declaratively in the store.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "699195e29c111b3c883bdfe53c5075ca59c1eb35ac56af7d756556d0b2458ed3",
+ "messageSha256": "699195e29c111b3c883bdfe53c5075ca59c1eb35ac56af7d756556d0b2458ed3",
+ "dateUtc": "2026-09-04T13:56:54Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "A statement of library goals. The report does not claim that a lint rule or migrations shipped.",
+ "windowNote": "4 September 2026, before the October period of this report; kept because it states the library's goals",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-state-lib-regions",
+ "outcome": "statelib-requests",
+ "report": "statelib",
+ "sourceRecord": "F4",
+ "language": "ru",
+ "ru": "еще интересно как мы реализуем описанную ранее идею разделения разных смысловых регионов стейта: busines, automations, ui state, и чтото еще вроде стоит выделить",
+ "en": "Also, I wonder how we will implement the idea described earlier of separating different semantic regions of state: business, automations, ui state, and maybe something else should be separated.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "3908f1e37cc4a46e13de2aa876b071026a6c00e629c582f0b06c779b011ece42",
+ "messageSha256": "d3afe37f08950cf6323fbb3b27e65f896a9652f41f6ca2770cea5fd5fc5a3908",
+ "dateUtc": "2026-09-04T12:14:43Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's Claude Code prompt history, project hyperide",
+ "senderBasis": "the owner's own prompt history; it has no per-message author field, so attribution is by account",
+ "messageIdentity": "no message id",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "A statement of library goals; no claim that this separation shipped.",
+ "windowNote": "4 September 2026, before the October period of this report; kept because it states the library's goals",
+ "omitted": "Only the first sentence of a longer multi-paragraph message is quoted.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-state-lib-compare",
+ "outcome": "statelib-numbers",
+ "report": "statelib",
+ "sourceRecord": "CG1",
+ "language": "ru",
+ "ru": "Надо сравнивать с ней и без нее по разным критериям более детально",
+ "en": "We need to compare with and without it in more detail, using different criteria.",
+ "excerptIsWholeMessage": false,
+ "excerptSha256": "370086710e66d5787cf30e745af375e9ffd3861156b1d378ada36d2cb32723bf",
+ "messageSha256": "8a4cf35e61141626bd5cf3164a66def49f813d75e1f7e5df0bedc366b0988296",
+ "dateUtc": "2026-10-16T13:33:01Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's archived ChatGPT conversation about StateLib and the Style Editor",
+ "senderBasis": "user message retrieved from the archived conversation and re-read against it on 7 November 2026; no message link was recovered",
+ "messageIdentity": "no message id recovered",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "The request behind the benchmark comparisons. It asks for measurement; the figures below are fixture measurements, not an editor speedup.",
+ "windowNote": "16 October 2026",
+ "omitted": "The rest of the message, including its first sentences and the request to write benchmark tests, is not quoted. The full message was re-read from the archived conversation on 7 November 2026; an earlier retrieved copy had added full stops, so its hash is not used.",
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ },
+ {
+ "id": "req-state-lib-numbers",
+ "outcome": "statelib-numbers",
+ "report": "statelib",
+ "sourceRecord": "CG2",
+ "language": "ru",
+ "ru": "Добро погнали. А также желательно чтобы оно давала и в цифрах более хорошей результаты работала быстрее бандл бы весил меньше",
+ "en": "All right, let's go. Also it would be good for it to give better results in numbers: to run faster and for the bundle to weigh less.",
+ "excerptIsWholeMessage": true,
+ "excerptSha256": "2c95bb365d37e75dbe0d3a50c6459febef805e388034a4bd7ddaeb9a5ecb8c59",
+ "messageSha256": "2c95bb365d37e75dbe0d3a50c6459febef805e388034a4bd7ddaeb9a5ecb8c59",
+ "dateUtc": "2026-10-16T16:32:19Z",
+ "author": "Ultra / Mextner",
+ "sourceLabel": "Ultra's archived ChatGPT conversation about StateLib and the Style Editor",
+ "senderBasis": "user message retrieved from the archived conversation and re-read against it on 7 November 2026; no message link was recovered",
+ "messageIdentity": "no message id recovered",
+ "ids": {},
+ "relationKind": "inferred",
+ "relation": "Same conversation. It states the wish for faster runs and a smaller bundle; the numbers below show what was measured, on fixtures.",
+ "windowNote": "16 October 2026",
+ "omitted": null,
+ "pastedEnglishBugText": false,
+ "permalink": null,
+ "permalinkNote": "private chat or local history: there is no public link and none is invented"
+ }
+ ],
+ "images": [
+ {
+ "id": "statelib-caption-before",
+ "path": "media/statelib-caption-before.png",
+ "sha256": "daae5e7300849ef6290ee45559cce9dcdf00a4f60bd847c235d086883ac17a0a",
+ "width": 720,
+ "height": 570,
+ "bytes": 83821,
+ "role": "before",
+ "pair": "statelib-caption",
+ "outcome": "live-docs",
+ "chip": "Before",
+ "alt": "StateLib docs diagram before the fix: the caption under the State diagram starts with b, a quote mark and a greater-than sign, then reads Safe label and diagram",
+ "caption": "Docs-site diagram caption before the fix.",
+ "roi": [
+ 60,
+ 40,
+ 300,
+ 230
+ ],
+ "scale": 3.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 76,
+ 179,
+ 268,
+ 202
+ ],
+ "crop_xyxy": [
+ 48,
+ 417,
+ 624,
+ 486
+ ],
+ "note": "The caption starts with the stray characters b\"> before the words Safe label."
+ }
+ ],
+ "original": {
+ "path": "images/caption-before.png",
+ "sha256": "51a7726c59072f657e954bad98e57c2beb9a37be0acf0353e6c2cc85f18348f7",
+ "width": 850,
+ "height": 450,
+ "bytes": 8337,
+ "source": "docs-site capture for #810 (earlier report image, unchanged)"
+ }
+ },
+ {
+ "id": "statelib-caption-after",
+ "path": "media/statelib-caption-after.png",
+ "sha256": "541f07cef6345ae8d06dd434cb9681845a09d2727c42106d2e1f3c03d3c2daeb",
+ "width": 720,
+ "height": 570,
+ "bytes": 81572,
+ "role": "after",
+ "pair": "statelib-caption",
+ "outcome": "live-docs",
+ "chip": "After",
+ "alt": "Same docs diagram after the fix: the caption reads Safe label, then diagram",
+ "caption": "Same fixture and crop after the fix.",
+ "roi": [
+ 60,
+ 40,
+ 300,
+ 230
+ ],
+ "scale": 3.0,
+ "boxes": [
+ {
+ "n": 1,
+ "original_xyxy": [
+ 76,
+ 179,
+ 243,
+ 202
+ ],
+ "crop_xyxy": [
+ 48,
+ 417,
+ 549,
+ 486
+ ],
+ "note": "The caption reads Safe label, then diagram."
+ }
+ ],
+ "original": {
+ "path": "images/caption-after.png",
+ "sha256": "8ae9f19a967ee8d48db6f8446cd827a5425a44c5a2c48f65c704d7127b7e57c5",
+ "width": 850,
+ "height": 450,
+ "bytes": 8199,
+ "source": "docs-site capture for #810 (earlier report image, unchanged)"
+ }
+ }
+ ],
+ "groups": [
+ {
+ "id": "statelib-caption",
+ "kind": "pair",
+ "crops": [
+ "statelib-caption-before",
+ "statelib-caption-after"
+ ]
+ }
+ ],
+ "videos": []
+}
diff --git a/reports/state-lib-2026-10/media/statelib-caption-after.png b/reports/state-lib-2026-10/media/statelib-caption-after.png
new file mode 100644
index 0000000..fd99556
Binary files /dev/null and b/reports/state-lib-2026-10/media/statelib-caption-after.png differ
diff --git a/reports/state-lib-2026-10/media/statelib-caption-before.png b/reports/state-lib-2026-10/media/statelib-caption-before.png
new file mode 100644
index 0000000..77f0f55
Binary files /dev/null and b/reports/state-lib-2026-10/media/statelib-caption-before.png differ
diff --git a/scripts/ci/_static_server.mjs b/scripts/ci/_static_server.mjs
new file mode 100644
index 0000000..68ace2a
--- /dev/null
+++ b/scripts/ci/_static_server.mjs
@@ -0,0 +1,61 @@
+/**
+ * Static file server shared by the report render checks (127.0.0.1 only, files below one root).
+ * It serves the MIME types of the report assets and answers HTTP byte ranges the way GitHub Pages does:
+ * 206 for a range, a suffix range counted from the end, 416 when no byte of the file is in range, and a
+ * malformed or reversed range ignored (plain 200), so the recordings can be loaded and seeked by every browser.
+ */
+import { readFileSync, statSync } from 'node:fs';
+import { extname, join, resolve, sep } from 'node:path';
+
+const MIME = {
+ '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json',
+ '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.mp4': 'video/mp4', '.webm': 'video/webm',
+};
+
+function parseRange(header, size) {
+ const m = /^bytes=(\d*)-(\d*)$/.exec(header ?? '');
+ if (!m || (m[1] === '' && m[2] === '')) return null;
+ if (m[1] === '') {
+ const n = Number(m[2]);
+ return n === 0 || size === 0 ? { unsatisfiable: true } : { start: Math.max(size - n, 0), end: size - 1 };
+ }
+ const start = Number(m[1]);
+ const end = m[2] === '' ? size - 1 : Math.min(Number(m[2]), size - 1);
+ if (m[2] !== '' && Number(m[2]) < start) return null;
+ return start >= size ? { unsatisfiable: true } : { start, end };
+}
+
+function respond(req, res, body, type) {
+ const range = parseRange(req.headers.range, body.length);
+ if (range?.unsatisfiable) {
+ return res.writeHead(416, { 'Content-Range': `bytes */${body.length}` }).end();
+ }
+ if (range) {
+ return res
+ .writeHead(206, { 'Content-Type': type, 'Accept-Ranges': 'bytes', 'Content-Range': `bytes ${range.start}-${range.end}/${body.length}`, 'Content-Length': range.end - range.start + 1 })
+ .end(body.subarray(range.start, range.end + 1));
+ }
+ return res.writeHead(200, { 'Content-Type': type, 'Accept-Ranges': 'bytes', 'Content-Length': body.length }).end(body);
+}
+
+/** Request handler for a static root: favicon 204, traversal 403, directory index.html, missing file 404. */
+export function staticHandler(root) {
+ const base = resolve(root);
+ return (req, res) => {
+ try {
+ const path = decodeURIComponent(new URL(req.url, 'http://x').pathname);
+ // No favicon is shipped for this static site; a 404 would register as a console error indistinguishable from a real bug.
+ if (path === '/favicon.ico') return res.writeHead(204).end();
+ let file = resolve(base, '.' + path);
+ if (file !== base && !file.startsWith(base + sep)) return res.writeHead(403).end();
+ if (statSync(file).isDirectory()) {
+ // Like GitHub Pages: a folder URL without its trailing slash is redirected, so relative asset paths keep working.
+ if (!path.endsWith('/')) return res.writeHead(301, { Location: `${path}/` }).end();
+ file = join(file, 'index.html');
+ }
+ return respond(req, res, readFileSync(file), MIME[extname(file)] ?? 'application/octet-stream');
+ } catch {
+ return res.writeHead(404).end('not found');
+ }
+ };
+}
diff --git a/scripts/ci/check-media-render.mjs b/scripts/ci/check-media-render.mjs
new file mode 100755
index 0000000..4358abd
--- /dev/null
+++ b/scripts/ci/check-media-render.mjs
@@ -0,0 +1,255 @@
+#!/usr/bin/env node
+/**
+ * Rendered contract for the screenshots, original requests and recordings in the three reports.
+ *
+ * What a reader gets, checked in a real browser (not only image.decode):
+ * - every annotated crop decodes, shows undistorted at its natural aspect ratio, contains the yellow
+ * annotation outline and real picture detail, and links to a full-size original that decodes and
+ * has the recorded size;
+ * - before and after crops of one comparison render at the same size (same region, same scale);
+ * - every owner request shows the visible labels "Original request (RU)" and "English translation";
+ * - every recording is a real