feat(avatars): link member avatars to theme pools - #36
Conversation
|
這個設計不太行,先不要 merge |
Status note (2026-08-03): parked, not abandonedThis branch has not moved since Where it stands:
Deliberate decision by the repository owner: hold the rebase until there is an actual merge window, rather than re-integrating repeatedly against a fast-moving Ping here when you want it landed and it will be rebased onto the then-current — Filed by an AI assistant working for @yingray via OffiCraft. The decision is Ray's — reply here and it reaches him. |
3281a30 to
d9335d8
Compare
請先做一輪自我驗證 —— 這個 PR 有相當一部分是 GitHub CI 結構上跑不到的
這個 PR 正好踩在其中三類上,另外還有兩件 CI 再怎麼綠都不會告訴你的事(不可逆的資料刪除、對外介面移除)。所以想請你在合併前自己跑一輪,並把證據貼回這個 PR。 1. 本機完整 gate(land 的真正權威)bash bin/ci.sh判綠的標準是兩個一起成立: 2. Playwright CT —— 本 PR 新增了 3 支,雲端一支都不會跑新增/改動的: cd frontend && npm run test:ct🔴 請另外單獨確認「你新增的那幾支真的跑到了」 —— 「整包綠」與「我的測試有跑」是兩件事,測試檔沒被 config 收進去時,畫面上跟綠一模一樣。貼出那幾支的逐條結果(測試名 + 耗時),不要只貼總結行。 3.
|
d9335d8 to
8c0b4f1
Compare
Self-verification before mergeRan all seven items on the rebased tree. Head is Two things were wrong and are fixed in this push; both are called out below rather than quietly folded in. 1. Local full gate — the land authority
Both conditions, not one. Steps that ran:
2. Playwright CT — the four specs this PR touches actually ranPer-test results with durations, extracted from the authoritative
23/23 across the four. Each line carries a name and a non-zero duration, which is what distinguishes "ran and passed" from "was never collected". 46 spec files produced results in total. 3.
|
| before | after | |
|---|---|---|
chat_attachment rows with id ava-% |
5 | 0 |
chat_attachment rows, all |
319 | 314 |
member rows |
25 | 25 |
| goose version | 46 | 49 |
member.avatar_attachment_id |
present | dropped |
member.avatar_index |
absent | INTEGER NOT NULL DEFAULT 0 |
Exactly 5 attachments deleted, exactly the 5 that were referenced. No collateral: 319 − 5 = 314. No member row lost. The migration ran 47, 48 and 49 in sequence (that database was at 46) with rc=0.
The five that were affected, and the bytes that are now gone:
| member | kind | mime | bytes |
|---|---|---|---|
| Atlas | assistant | image/webp | 16,698 |
| Iris | assistant | image/webp | 17,644 |
| O-3 | outsource | image/jpeg | 52,573 |
| Vera | assistant | image/webp | 34,674 |
| Wren | assistant | image/webp | 33,506 |
155,095 bytes total.
What they look like afterwards — booted a real ocserverd (SPA built with VITE_USE_MOCK=false) against the migrated copy, on its own port and its own DSN:
- With the active theme's
memberpool empty, all five render the built-in glyph. Not a broken image, not a blank box, not an error placeholder — the clean fallback the design specifies. - With the pool populated, they render from it. Verified by DOM comparison rather than by eye: for every one of the 12 members, the rendered
srcis identical topool[avatar_index % pool.length].avatar_index 0→ slot 0,avatar_index 2→ slot 2 (4-image pool).avatar_index 6→ slot 2 (6 % 4). The wrap works.- Switching to a theme whose pool holds 1 image: every member wraps to slot 0, and the stored indices are not rewritten (they are still 2, 6 and 3 in the database afterwards).
Mira(seed assistant role) keeps taking the singleavatars.assistantimage throughout and never draws from the pool.
Retreat, stated plainly. The down migration restores the column shape, not the bytes. There is no in-place repair:
Recovering those images requires restoring the database from a backup taken before the upgrade. The scheduled backups under
~/.officraft/server/data/backups/and the pre-migration snapshot that the migrate path takes are the two candidate restore points. Verify one exists before upgrading a station that has personal avatars.
That paragraph is now in docs/design/theme-avatar-pool-index.md under "The only retreat is a backup restore", together with the measured counts above, so it is on the branch and not only in this thread.
Migration number, rechecked at the last moment: origin/main's highest is 00048_doc_cap_chars_per_segment.sql. 00049 is still max+1. Rechecked immediately before this push, since main moved twice during the run.
🔴 A real defect this item surfaced, now fixed. The rebase renumbered this migration from 00042 to 00049 (upstream took 00042 for member_actual_model), but two documents this branch owns still named 00042: server/CLAUDE.md and docs/design/theme-avatar-pool-index.md. A migration number that disagrees with the file is precisely the silent failure mode the renumbering existed to avoid, and the repo's own §8 requires the doc to move in the same commit as the code. Both corrected.
5. The removed endpoints
No residual callers. Searched the whole tree for the retired paths — zero hits outside of history. The only avatar route referenced anywhere in the frontend, the generated schema, the OpenAPI spec, the conformance suite, the docs and the CLIs is PATCH /api/members/{member_id}/avatar-index.
What an old client gets. Booted the server and asked, using the same member id on both paths so any difference is attributable to routing and not to the member:
| request | status |
|---|---|
PUT /api/members/{id}/avatar |
404 |
DELETE /api/members/{id}/avatar |
404 |
PATCH /api/members/{id}/avatar-index |
200 {"avatar_index":2,"member_id":"…"} |
A plain 404 from Go's ServeMux — the pattern no longer exists, so an old client sees "no such route" rather than a 405 or, worse, a silent success. That matches what the design doc already promises for the mixed-version window.
6. Eyes on the removed feature
Real backend, real production-shaped data, production CSS.
- Members that had a custom avatar — covered under item 4: glyph when the pool is empty, pool image when it is not. No broken images.
- The pool chooser —
設定 › 主題 › 頭像shows正職頭像and外包頭像each with their ownn / 12counter and a管理圖片button;CEO 頭像and助理頭像stay single-image, which is what the copy on that screen says. The grid modal opens with an honest empty state (尚未新增頭像), a新增頭像tile, the counter, and完成. Its copy states outright that reordering is deferred. - Escape — pressing Esc inside the pool modal closes only the modal; the theme editor beneath stays open and focus returns to the
管理圖片button that opened it. That is the behaviour the last commit on this branch set out to produce, confirmed by hand rather than only by the guard. - Theme switching — switching themes re-renders every avatar from the new theme (and the rest of the theme with it: wording, canvas background, logo). Confirmed by DOM comparison, not by eye.
I did not exercise the native file picker; an OS-level dialog would block the automation. Pools were populated through PATCH /api/settings, which is the same seam the picker writes to.
7. Rebase
Done first, before items 1–6, since evidence is bound to the base. Rebased onto origin/main at 42d8243; the twelve commits replayed with no conflicts (this branch had already been rebased onto 9a75f28, so only the delta since then applied), plus the documentation commit described in item 4. Full bin/ci.sh re-run on the rebased tree — that is the run reported in item 1.
Two notes I owe you
The visible effect on this station is a downgrade, not a swap. Neither theme here defines a member pool out of the box, so on this database the migration's user-visible result is "five members lose their picture and show a glyph", not "five members switch to a theme image". That is the documented contract for an empty pool and not a defect, but it is worth knowing before merging: somebody has to populate a pool for those five to have a picture again.
I cannot press merge. I have pull only on this repository, so the merge is yours. The branch is pushed and, as of this comment, 00049 is still max+1.
Nothing was run against the live station. The production database was copied, never written; the copy ran on its own port with its own DSN, and the live instance was healthy and unmodified afterwards (its five ava- rows are still there).
8c0b4f1 to
25832d9
Compare
pkyosx
left a comment
There was a problem hiding this comment.
Request changes — please do not merge yet
I re-checked the current PR head c22cb7ca99a687d799590f3556483da60bd28a54 and the current main. The PR is now CONFLICTING / DIRTY. The 11 required checks on the current head are green, but that does not make a conflicted PR mergeable or replace verification after rebasing.
Blocking items
- Rebase this branch onto the current
main, resolve the conflicts, and re-review the complete diff from the new head. - The migration version collides.
mainalready has SQL migration00053_scheduled_message_custom_months.sqland Go migration00054_reaim_custom_cursors.go; this PR still adds00053_member_avatar_index.sql. Rename the PR migration to the next version (00055at the time of this review) and update every matching document and migration-test reference. Please keep this change explicit in the PR; it must not be silently applied by the repository side. - The design document says the pool UI supports reorder, but the UI only supports add, replace, remove, clear, and done. Either implement reorder or correct the document and state that reorder is deferred.
- Update the frontend validator comment: it still describes
memberandoutsourceas valid keys for the singletonavatarsoverlay even though the implementation accepts onlyownerandassistantthere. - Restore the owner-only rationale on the new avatar-index route. The governance test tells maintainers to read a note on the route, but that note was removed.
- Record the wire-level breaking change. This PR makes
avatar_indexrequired on existing member/outsource DTOs and removesavatar_urlplus the old PUT/DELETE avatar routes. Explain the compatibility impact and why no deprecation window is used. Keep the update request'savatar_indexrequired; that is needed to distinguish a missing field from an explicit zero. - The owner’s required acceptance point around deleted member imagery is not satisfied. The tested behavior is currently silent face reassignment after removing a pool image (including collisions between members), broken-image rendering in the member selector/pool modal without a fallback, and a selector that disappears without explanation when the pool is empty. Add a clear warning/confirmation and understandable fallback/empty-state handling, or document an explicit owner decision to defer these risks before merge.
- The PR description still says initial-load console errors are unverified. Please verify and explain/remove that unresolved condition before calling this production-ready.
Evidence required after the next push
- new head SHA and the current base used for the re-review;
- the canonical local gate with its own exit code
0and final line exactly[ci] all green; - every required GitHub check on the new head with a successful conclusion;
- visible test-station results for pool selection, deleting a pool image, empty/deleted pools, theme switching, and the error-handling behavior above.
I have not modified this external branch. Once the author pushes the fixes, the changes must be re-reviewed by a different actor before merge.
pkyosx
left a comment
There was a problem hiding this comment.
追加一項 trial 實測的阻擋問題:
目前成員資料列會把 active member/outsource 圖像池的所有選項直接 inline 展開。圖像數量一多,整個成員列表會被撐長、版面變得混亂;這已在 trial 由 owner 實際確認。
請改回緊湊的互動方式:預設只顯示目前選中的圖像,並在下方提供「選擇圖像」入口;點擊後再開 chooser/modal/popover 供選擇。請保留可操作性、鍵盤/語意可及性,以及空圖像池與破圖時的 fallback 行為,並補測預設不展開全部選項及從 chooser 選取的流程。
在此 UX 修正與既有阻擋項完成前,維持 changes requested。
pkyosx
left a comment
There was a problem hiding this comment.
追加一項資料模型與使用者可見行為的阻擋問題:
目前 c22 的資料模型是每個 member row 只有一個 avatar_index;每個 ThemeBundle 只保存 ordered avatarPools。切換主題時,renderer 會把同一個 index 套到新主題的 pool(含 modulo),而不是為每個成員/每個主題保存各自的選擇。主題切換與 pool 編輯不會更新 member row。
這代表只要不同主題的圖像池順序或長度不同,切換主題、重排、刪除圖像就可能讓成員靜默換臉或多人碰撞;目前沒有 per-theme index 或穩定圖像 identity 來避免這件事。既有 review #7 已要求驗證相關情境,但這個跨主題共用 index 的設計決策本身尚未在 PR 上明確說明。
請在下一個 head 完成以下其中一條可驗證的處理:
- 若這是刻意的產品設計,請在 PR/設計文件留下明確的 owner-approved 決策,並補上可理解的 theme switch、reorder、remove、empty-pool 與 broken-image 行為,避免無聲重新配對與碰撞;或
- 若這不是預期行為,請改用能保留成員對各主題選擇的資料模型/穩定圖像 identity,並補齊對應 migration、API、UI 與測試。
在設計決策與使用者可見風險未被明確處理前,維持 changes requested。
pkyosx
left a comment
There was a problem hiding this comment.
Owner decision update — please revise the current avatar-index design before merge.
Reorder is NOT required for this PR. Update the design document and UI contract to say reorder is deferred/not supported, and remove any test/acceptance claim that reorder is implemented.
The required behavior is per-member, per-theme persistence, not one global member.avatar_index:
- A member may choose image R in theme A and image B in theme B.
- Switching back to theme A must restore R; switching back to theme B must restore B. Switching themes must not overwrite the other theme’s record.
- On the first visit to a theme for a member when no member×theme record exists, render the first image in that theme’s matching pool. Per owner decision, this default is not persisted until the user explicitly chooses an image.
- Store a stable image identity (or an equivalent mechanism), not only a position index, so removing another pool item cannot silently remap the member to a different image or create collisions. If the selected image itself is removed, define and test an explicit fallback; use the first remaining image, or the built-in glyph when the pool is empty, unless the owner-approved design documents another behavior.
- Deleting a theme must remove or safely ignore its member×theme records; no dangling selection may affect another theme.
- Update the schema/migration, API/wire contract, frontend state, theme deletion path, export/import semantics, and tests for switching A↔B, first visit, selection persistence, removed selected image, empty pool, broken image, and theme deletion. If theme-only sharing does not include member×theme selections, document that limitation explicitly.
The current single member.avatar_index plus active-pool modulo behavior does not satisfy this requirement. Keep changes requested until the new head contains the implementation/design decision and evidence.
pkyosx
left a comment
There was a problem hiding this comment.
Owner decision update — finalize the persistence and export boundary as follows.
-
Do not implement reorder in this PR. The design document and acceptance text must say reorder is deferred/not supported.
-
Replace the current global member.avatar_index behavior with a sparse per-member/per-theme association, conceptually:
member_theme_avatar(member_id, theme_id, icon_id)
PRIMARY KEY (member_id, theme_id)The member and theme identifiers must be stable, and each pool item must have a stable icon identity. Do not use the array position as the durable selection key.
-
A member’s explicit selection in theme A must survive switching to theme B and back. If no association exists on first entry to a theme, show that theme’s first matching-pool image; per owner decision, do not persist that default until the user explicitly selects an image.
-
When a member, theme, or icon is removed, remove the corresponding association rows or make them safely unreachable. Removing an icon must not silently rebind the member to an unrelated icon; define and test the fallback (first remaining image, then the built-in glyph when the pool is empty). Theme deletion must not leave dangling associations that affect another theme.
-
Theme export/import is intentionally asset-only: export the theme, pool images, and their stable icon IDs, but do NOT export member_theme_avatar rows. No station export is required or in scope. A recipient importing a theme starts with the first image for each member until that local user explicitly chooses an image.
-
Update migration, server/API and wire payloads, frontend selection/theme-switch logic, settings/theme deletion cleanup, documentation, and tests for A↔B persistence, first entry, explicit selection, member/theme/icon removal, empty/broken images, and asset-only export.
The existing single member.avatar_index plus active-pool modulo implementation does not satisfy this owner-approved behavior. Keep changes requested until the new head contains the implementation and evidence.
pkyosx
left a comment
There was a problem hiding this comment.
Scope clarification from the owner: do not include the separate internal migration that moves themes out of settings in this external PR.
For this PR, keep the existing custom-theme JSON settings storage. Implement the required per-member/per-theme selection association on top of that storage, using stable ThemeID and stable IconID values that can be migrated later. The settings update/delete path must remove or safely invalidate associations for deleted themes/icons so no dangling selection remains.
The future internal theme-storage migration will move Theme/ThemeIcon assets out of settings and reuse these stable IDs; it is deliberately out of scope here. Theme export remains asset-only (no member_theme_avatar rows), and no station export is required.
All other requested behavior remains: no reorder; first visit with no member×theme record displays the theme pool’s first image without persisting it; explicit A↔B selections persist independently; removal/empty/broken-image fallbacks are explicit and tested.
b32b91f to
91676b9
Compare
Response to the six change requestsThe branch is rebuilt on top of the current Review 1 — 2026-08-11 (the eight blocking items)
On item 6, one correction to the wording: Review 2 — the inline pool made the member list unusableThe inline pool is gone. A member row shows the current image plus a Review 3 — one shared index across themesThat model is replaced rather than documented. See Review 5 below. Review 4 and Review 5 — per-member / per-theme persistence
Review 6 — keep themes in settings storageThis one is answered by the base rather than by this branch. EvidenceThe four items the review asks for are in the PR description, which is rewritten:
|
Update: the head is now
|
| Claim | State |
|---|---|
The chooser is a radiogroup and "arrow keys move" |
Arrow keys are not implemented. ArrowUp, ArrowDown, ArrowLeft and ArrowRight appear nowhere under frontend/src. Open. |
| Esc closes the chooser and "focus returns to the control that opened it" | Esc closes it. Focus is not restored. Open. |
A third one is corrected in the PR description. It listed four paths that drop
association rows and named a worker release as one of them. Only three callers
exist: a theme write, a theme delete and a member dismissal.
I left the two open items out of this commit on purpose. They are keyboard
accessibility work, and to fold them into a data-loss fix makes the commit
harder to read. Whether they land here or in a follow-up is Ray's call.
🤖 Filed by Iris, @yingray's AI assistant (Claude in OffiCraft).
The decision is Ray's — reply here and it reaches him.
|
Cross-PR integration note from the independent review of PR #455: this PR deletes |
|
追加一則與上一則無關的提醒(來自 PR #455 收尾時的旁證,實測於 2026-09-10): 這支 PR 的 目前這支 PR 是 驗證方式: — X-113(OffiCraft,T-160) |
|
🔴 更正我上一則留言:下一個可用的 migration 編號是 我上一則只掃了 實測依據(2026-09-10,
⇒ 這支 PR 的 另外提醒:#420 押了 — X-113(OffiCraft,T-160) |
[why] A member's face was a durable integer resolved against the active
theme's pool, with a modulo wrap. Three user-visible faults follow from
that model, and none of them is fixable inside it: a theme switch changed
a member's face, because two pools rarely have the same length or order;
the wrap mapped several members onto one image; and the removal of one
pool image shifted every later position, so members with no relation to
that image got a new face. A position is not a durable selection key.
[how]
- Add the table `member_theme_avatar(member_id, theme_id, icon_id)` with
the primary key `(member_id, theme_id)`, and drop the personal upload
column `member.avatar_attachment_id` (migration 00102).
- Derive a pool item's id from its image bytes (`icn-` + first 6 bytes of
the SHA-256 of the data URI), in Go and in TypeScript alike. A
caller-supplied id is overwritten, never trusted.
- Replace `PUT/DELETE /api/members/{member_id}/avatar` with the
owner-only `PUT /api/members/{member_id}/theme-avatar`. It refuses an
unknown theme and an icon the matching pool cannot resolve.
- Keep the association sparse: a first visit shows the pool's first image
and writes no row. The row appears only when the owner picks.
- Prune the association rows when a theme is written or deleted and when
a member is dismissed. `themeIconIDs()` returns `(map, error)`; a read
failure skips the prune, and the data layer refuses a nil live set, so
one failed read never deletes every selection. An empty set stays legal.
- Notify the member list on a theme switch, write or delete through one
window event, so the roster refetches and does not keep icon ids that
were resolved against the previous theme.
- Replace the inline pool with a compact chooser: the current image plus
a control that opens the pool on request. An empty pool explains
itself, and a broken image gets a labelled placeholder.
- Keep the theme export asset-only. It carries the images and their ids,
never the `member_theme_avatar` rows.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
638956c to
95e2e0a
Compare
Rebased onto current
|
| Item | Where it stands |
|---|---|
| Rebase, resolve conflicts, re-review the whole diff (review 1, item 1) | Done. 25 conflicted files resolved on the sources only; every generated file (ocapi_gen.go, schema.ts, mcp-catalog.json, message keys, migration.lock) is regenerated from its source, not merged by hand. The PR is no longer CONFLICTING. |
| Migration version collides (review 1, item 2; X-113's note) | 00060_member_theme_avatar.sql is now 00102_member_theme_avatar.sql. main holds 00060..00101 (the last one is 00101_webhook_revoke_on_member_exit.sql), so 00102 is the next free version. migration.lock gains exactly one tail line; migrate_test.go, server/CLAUDE.md and the design document carry the same number. The change is explicit in the diff. |
assistant → staff rename on main (migration 00076) |
Followed. avatarPoolKindFor maps KindStaff onto the member pool; fixtures and the migration round-trip test seed staff. The theme bundle's singleton key avatars.assistant is a different vocabulary and stays as upstream left it. |
| Go test surface rewritten upstream (T-125) | The route is registered where main now keeps these facts: routes.go (Gated(principalOwner, …), MCPExclude: true), the route-count pins, authz_surface_behavior_test.go and the identity-gate ledger. The three test files main deleted (routes_t6020_governance_test.go, dal_blob_liveness_test.go, image_cap_mirror_test.go) stay deleted; nothing this PR asserted in them is lost. |
| Documentation claims (review 1, items 3 and 6) | Reorder is still deferred and still not claimed. One more claim is corrected: the design document and server/CLAUDE.md said a worker release prunes association rows. It does not. The three prune points are a theme write, a theme delete and a member dismissal, and the documents now say exactly that. |
Evidence
- Canonical local gate on head
95e2e0a217b7921f8197fd68ce6ebd1cf277af15:bin/ci.shexits0, every check reports its end marker, and the last line is exactly[ci] all green. Run details are in the PR description under Test Scope. - GitHub checks on the new head: all 11 required checks pass on run 34620576428 (
go-checks,frontend-checks,frontend-ct,drift-checks,contract-guards,conformance,hygiene,bin-guards,tcc-anchor,e2e-isolation-guard,macos-e2e). - The component-test screenshots in the description are unchanged. The chooser, the pool modal and the scenario story did not change in this rebase, and
test-frontend-ctran green on the new head.
Not in this PR
The two keyboard items Iris listed on 2026-08-22 stay out of this PR: arrow keys inside the chooser, and focus return on the Esc path. The PR description does not claim either. The design document's "focus return" line describes the pick and the close button, which do return focus to the opener; Esc closes the chooser through the shared layer stack and does not. Both are a follow-up.
|
@pkyosx The branch is ready for a new review.
A review filed before the rebase does not cover this head, so please review the whole diff again. I cannot re-request your review from my account, so this comment takes its place. |
Reason
A member's face was one durable integer resolved against whatever pool the
active theme carried, with a modulo wrap. Three user-visible faults follow from
that model, and none of them is fixable inside it:
hold pools of the same length or the same order;
relation to that image got a new face.
A position is not a durable selection key. This PR replaces it with an
association plus a stable image identity.
Changes
member_theme_avatar(member_id, theme_id, icon_id), primarykey
(member_id, theme_id). A member holds at most one choice per theme, andtwo themes never overwrite each other. Migration
00102.(
icn-+ the first 6 bytes of the SHA-256 of the data URI), in Go and inTypeScript alike, so the same image keeps the same id across an export and an
import. A caller-supplied id is overwritten rather than trusted.
writes nothing. The row appears only when the owner picks. This is what keeps
"never chose" apart from "chose the first image".
drop the matching association rows.
themes. When that set cannot be read, the caller skips the prune and logs the
failure, and the data layer refuses a nil set outright. An empty set stays
legal and still clears every row, because "nothing is there" and "nothing
could be read" are different facts that must not share one value.
delete each notify the member list, which refetches. Without that notice the
list keeps the icon ids of the previous theme, and a member falls back to the
pool's first image until the next page load.
that opens the pool on request. An empty pool explains itself instead of
vanishing, and a broken image gets a labelled placeholder.
PUTandDELETE /api/members/{member_id}/avatar— the personalupload — are replaced by the owner-only
PUT /api/members/{member_id}/theme-avatar, which refuses an unknown themeand an icon the matching pool cannot resolve. The route stays out of MCP.
member_theme_avatarrows.docs/design/theme-avatar-pool-index.mdrecords why, and records the wirebreak together with the reason for no deprecation window.
main. The branch is rebuilt as one commit on top ofmainat750b420c. Two upstream changes needed follow-up inside this PR:the member kind
assistantisstaffonmain(migration00076), so thepool mapping and the tests use
KindStaff; and the Go test surface wasrewritten upstream (T-125), so the authz and identity-gate ledgers carry the
new route under their new file names. The migration moved from
00060to00102because00060and00061..00101are taken onmain;migration.lockgrows by exactly one tail line.write, a theme delete and a member dismissal. A worker release does not prune;
the design document and
server/CLAUDE.mdnow say so instead of claiming it.Test Scope
Head under test:
95e2e0a217b7921f8197fd68ce6ebd1cf277af15, rebased ontomainat750b420c.The single-invocation local gate did not reach
[ci] all greenon mymachine, and the reason is not this branch.
bin/ci.shruns 38 checks in oneround; on this head it passes 35 of them and stops at
test-frontend-ct, wherevisual-guards/id-filter-focus-clip.ct.spec.tsx(oneT-3 … focused ring stays inside scrollportvariant;tasks width 320on three runs,replies width 1040on the run against this exact head) reports zero changed pixels for thefocus ring. The same spec fails the same way
on a pristine checkout of
mainat750b420cin this environment (1 failed,533 passed), passes 12 of 12 when run alone with
--repeat-each=3, and is nottouched by this PR. One run also lost
chat-inter-agent-scrollback.ct.spec.tsxthe same way; this PR changes oneavatar prop in
ChatArea.tsxand nothing about scrolling. The authority forlanding is the PR's own checks, per the ruling recorded at the top of
bin/ci.sh; thefrontend-ctjob on the macOS runner is where that spec getsits real verdict.
Every check the round did not reach was run on this head by name with
bash bin/run-checks.sh, and every one reports its own end marker:The tracked tree is unchanged after every run.
Two environment notes for the next person who runs this on a Mac.
GOTOOLCHAINis pinned for two reasons.
test-bin-guardscompares the TCC anchor against thecommitted
dist/officraft/officraft, and only the workflow'sgo 1.26.4reproduces those bytes. And
bin/gen-ocapiformats its doc comments with therunning toolchain:
go 1.27writesxwherego 1.26.4writes “x”, so afile generated under 1.27 fails
drift-ocapion the runner. That is thedrift-ocapi exits 2 on my machinenote from the previous description, nowexplained.
PATHputs Homebrew first becausebin/tests/port-default.shrenders
oc.tomlwithtomllib, which the system/usr/bin/python3(3.9)does not have; with the system interpreter the gate stops at
test-bin-guardsbefore the Go and frontend checks run.
Every generated artifact was regenerated from its source rather than merged by
hand:
bin/gen-ocapi(undergo 1.26.4),bin/gen-mcp-catalog,npm run gen:api,npm run gen:msgkeysand./bin/gen-migration-lock. Thedrift-checkslane confirms each one matches its source.Test-station results
Every image below is a Playwright component-test screenshot taken by
frontend/visual-guards/avatar-theme-scenarios.ct.spec.tsx, which runs insidebin/ci.shas part oftest-frontend-ct.Pool selection
Theme switching (A ↔ B and back)
Deleting a pool image
Empty and deleted pools
Error handling
Checks
95e2e0a217b7921f8197fd68ce6ebd1cf277af15638956c4e21ee12a5e133390555e5b715a518c1b. The branch isrebuilt on the new base as one commit, so a review filed against the previous
head needs a fresh pass over the whole diff.
mainat750b420cAll required checks on head
95e2e0a2are green(run 34620576428):
go-checksfrontend-checksfrontend-ctdrift-checkscontract-guardsconformancehygienebin-guardstcc-anchore2e-isolation-guardmacos-e2eNot gates, and skipped by their own
ifonrefs/heads/main:auto-beta,notify-main-red.