Преглед на файлове

test(web): separate session snapshots from goldens

Tianyi Cui преди 1 месец
родител
ревизия
6189e4a374
променени са 100 файла, в които са добавени 62 реда и са изтрити 62 реда
  1. 2 2
      .agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.i18n.yaml
  5. 1 1
      .agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md
  6. 1 1
      .agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.zh.md
  7. 2 2
      .agents/notes/implemented/feature/2026-07-30-web-search-card.i18n.yaml
  8. 1 1
      .agents/notes/implemented/feature/2026-07-30-web-search-card.md
  9. 1 1
      .agents/notes/implemented/feature/2026-07-30-web-search-card.zh.md
  10. 2 2
      .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml
  11. 1 1
      .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md
  12. 1 1
      .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md
  13. 1 1
      apps/web/tests/access-confirmation.e2e.ts
  14. 1 1
      apps/web/tests/agent-preset-authoring.e2e.ts
  15. 1 1
      apps/web/tests/agent-preset-selection.e2e.ts
  16. 1 1
      apps/web/tests/approval-composer.e2e.ts
  17. 2 2
      apps/web/tests/background-job-list.e2e.ts
  18. 1 1
      apps/web/tests/bash-abort-row.e2e.ts
  19. 2 2
      apps/web/tests/code-mode-round.e2e.ts
  20. 1 1
      apps/web/tests/cold-blank-session.e2e.ts
  21. 1 1
      apps/web/tests/composer-draft-scroll.e2e.ts
  22. 1 1
      apps/web/tests/composer-tab-geometry.e2e.ts
  23. 1 1
      apps/web/tests/conversation-column-overflow.e2e.ts
  24. 2 2
      apps/web/tests/cordis-tool-round.e2e.ts
  25. 2 2
      apps/web/tests/declared-reasoning.e2e.ts
  26. 3 3
      apps/web/tests/details-session-lifecycle.e2e.ts
  27. 1 1
      apps/web/tests/feedback-command.e2e.ts
  28. 1 1
      apps/web/tests/github-ready-review.e2e.ts
  29. 1 1
      apps/web/tests/goal-bar.e2e.ts
  30. 2 2
      apps/web/tests/goal-command-presentation.e2e.ts
  31. 1 1
      apps/web/tests/goal-multi-turn-actions.e2e.ts
  32. 0 0
      apps/web/tests/goldens/access-confirmation/ui.expected.md
  33. 0 0
      apps/web/tests/goldens/agent-preset-authoring/copy-dialog.expected.md
  34. 0 0
      apps/web/tests/goldens/agent-preset-authoring/created.expected.md
  35. 0 0
      apps/web/tests/goldens/agent-preset-authoring/damaged.expected.md
  36. 0 0
      apps/web/tests/goldens/agent-preset-authoring/section.expected.md
  37. 0 0
      apps/web/tests/goldens/agent-preset-selection/header.expected.md
  38. 0 0
      apps/web/tests/goldens/agent-preset-selection/hero.expected.md
  39. 0 0
      apps/web/tests/goldens/agent-preset-selection/menu.expected.md
  40. 0 0
      apps/web/tests/goldens/cold-blank-session/sidebar.expected.md
  41. 0 0
      apps/web/tests/goldens/composer-draft-scroll/geometry.expected.md
  42. 0 0
      apps/web/tests/goldens/composer-tab-geometry/geometry.expected.md
  43. 0 0
      apps/web/tests/goldens/conversation-column-overflow/geometry.expected.md
  44. 0 0
      apps/web/tests/goldens/declared-reasoning/ui.expected.md
  45. 0 0
      apps/web/tests/goldens/github-ready-review/conversation.expected.md
  46. 0 0
      apps/web/tests/goldens/goal-bar/active.expected.md
  47. 0 0
      apps/web/tests/goldens/goal-command-presentation/ui.expected.md
  48. 0 0
      apps/web/tests/goldens/home-path-tilde/workspace-hover.expected.txt
  49. 0 0
      apps/web/tests/goldens/markdown-cjk-strong/ui.expected.md
  50. 0 0
      apps/web/tests/goldens/markdown-images/ui.expected.md
  51. 0 0
      apps/web/tests/goldens/markdown-inline-code-links/ui.expected.md
  52. 0 0
      apps/web/tests/goldens/markdown-wide-table/geometry.expected.md
  53. 0 0
      apps/web/tests/goldens/math-rendering/ui.expected.md
  54. 0 0
      apps/web/tests/goldens/max-tokens-notice/history-turn.expected.txt
  55. 0 0
      apps/web/tests/goldens/models-settings/configured.expected.md
  56. 0 0
      apps/web/tests/goldens/models-settings/declared-edit.expected.md
  57. 0 0
      apps/web/tests/goldens/models-settings/declared.expected.md
  58. 0 0
      apps/web/tests/goldens/models-settings/delete.expected.md
  59. 0 0
      apps/web/tests/goldens/models-settings/empty.expected.md
  60. 0 0
      apps/web/tests/goldens/models-settings/model-picker.expected.md
  61. 0 0
      apps/web/tests/goldens/models-settings/native-delete.expected.md
  62. 0 0
      apps/web/tests/goldens/onboarding-deepseek-config/missing.expected.md
  63. 0 0
      apps/web/tests/goldens/onboarding-deepseek-config/models.expected.md
  64. 0 0
      apps/web/tests/goldens/onboarding-deepseek-config/welcome.expected.md
  65. 0 0
      apps/web/tests/goldens/onboarding-usable-provider/dismissed.expected.md
  66. 0 0
      apps/web/tests/goldens/plugin-config/section.expected.md
  67. 0 0
      apps/web/tests/goldens/reference-composer/caret-edits.expected.md
  68. 0 0
      apps/web/tests/goldens/reference-composer/menu.expected.md
  69. 0 0
      apps/web/tests/goldens/reference-composer/order.expected.md
  70. 0 0
      apps/web/tests/goldens/schedule-after/at-conversation.expected.md
  71. 0 0
      apps/web/tests/goldens/schedule-after/conversation.expected.md
  72. 0 0
      apps/web/tests/goldens/schedule-after/every-conversation.expected.md
  73. 0 0
      apps/web/tests/goldens/search-card/grep-card.expected.txt
  74. 0 0
      apps/web/tests/goldens/settings-chrome/dialog-en.expected.md
  75. 0 0
      apps/web/tests/goldens/settings-chrome/dialog.expected.md
  76. 0 0
      apps/web/tests/goldens/settings-chrome/plugins.expected.md
  77. 0 0
      apps/web/tests/goldens/sidebar-subagent-activity/owner-running.expected.md
  78. 0 0
      apps/web/tests/goldens/skill-invocation-policy/menu.expected.md
  79. 0 0
      apps/web/tests/goldens/skill-user-invoke/ui.expected.md
  80. 0 0
      apps/web/tests/goldens/stats-paged-history/ui.expected.md
  81. 0 0
      apps/web/tests/goldens/steer-all/mid-steer.expected.md
  82. 0 0
      apps/web/tests/goldens/steer-all/replay.override.json
  83. 0 0
      apps/web/tests/goldens/steer-all/settled.expected.md
  84. 0 0
      apps/web/tests/goldens/todo-row/parallel-plan.expected.txt
  85. 0 0
      apps/web/tests/goldens/trajectory-virtualization/load-more.expected.md
  86. 0 0
      apps/web/tests/goldens/web-runtime-context/file-reference-prompt.expected.md
  87. 0 0
      apps/web/tests/goldens/web-runtime-context/web-surface-prompt.expected.md
  88. 1 1
      apps/web/tests/home-path-tilde.snapshot.ts
  89. 1 1
      apps/web/tests/lifecycle-chrome.e2e.ts
  90. 1 1
      apps/web/tests/live-interactions.e2e.ts
  91. 2 2
      apps/web/tests/markdown-cjk-strong.e2e.ts
  92. 2 2
      apps/web/tests/markdown-images.e2e.ts
  93. 2 2
      apps/web/tests/markdown-inline-code-links.e2e.ts
  94. 2 2
      apps/web/tests/markdown-wide-table.e2e.ts
  95. 2 2
      apps/web/tests/math-rendering.e2e.ts
  96. 1 1
      apps/web/tests/max-tokens-notice.snapshot.ts
  97. 2 2
      apps/web/tests/message-actions.e2e.ts
  98. 2 2
      apps/web/tests/message-feedback-layout.e2e.ts
  99. 1 1
      apps/web/tests/message-feedback-protocol.snapshot.ts
  100. 1 1
      apps/web/tests/message-feedback.e2e.ts

+ 2 - 2
.agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.md
-2026-07-30-command-row-copy-contract.md: f6d5199389b3907780c501894e2861e6add85e77
-2026-07-30-command-row-copy-contract.zh.md: 86f0c044e841a6a0148b1f785582868bba5bb3d0
+2026-07-30-command-row-copy-contract.md: d410adf62dbbc4445936c70309d5902bc184b16f
+2026-07-30-command-row-copy-contract.zh.md: 0f680f9beb8b335dd6311cadc6c7e022809fb8f2

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.md

@@ -32,4 +32,4 @@ The log is unchanged: `command/run` keeps the structured `name`/`args` split, so
 
 
 ## Consequences
 ## Consequences
 
 
-Every command row gets shorter, and the rule scales: a new command's author writes its outcome without knowing which surface renders it, and no surface has to de-duplicate. The cost is that the dispatched arguments leave the collapsed row — while a command is still executing the row shows only its name and `执行中…` — and that the no-caption rule is a convention the reviewer enforces, not a gate. The `/permission` texts are pinned by the permission package's command tests, and the assembled row copy by the [seeded-history](../../../../apps/web/tests/snapshots/seeded-history/command-row.expected.md) web golden, which reaches a real settled command row keylessly because `/permission` runs entirely on the host.
+Every command row gets shorter, and the rule scales: a new command's author writes its outcome without knowing which surface renders it, and no surface has to de-duplicate. The cost is that the dispatched arguments leave the collapsed row — while a command is still executing the row shows only its name and `执行中…` — and that the no-caption rule is a convention the reviewer enforces, not a gate. The `/permission` texts are pinned by the permission package's command tests, and the assembled row copy by the [seeded-history](../../../../snapshots/web/seeded-history/command-row.expected.md) web golden, which reaches a real settled command row keylessly because `/permission` runs entirely on the host.

+ 1 - 1
.agents/notes/implemented/architecture/2026-07-30-command-row-copy-contract.zh.md

@@ -32,4 +32,4 @@ Web 命令条目由一对落库的[命令生命周期事件](../../proposed/arch
 
 
 ## 后果
 ## 后果
 
 
-每一条命令条目都变短了,而且这条规则可扩展:新命令的作者写结果时无需知道由哪个界面渲染,任何界面也都不必再去重。代价是分派参数离开了折叠行——命令仍在执行时,行上只有名字和 `执行中…`——以及「不加题头」这条规则是靠评审执行的约定,而非门禁。`/permission` 的文案由 permission 包的命令测试钉住,装配后的行文案由 [seeded-history](../../../../apps/web/tests/snapshots/seeded-history/command-row.expected.md) web 预期输出钉住:由于 `/permission` 完全在 host 上执行,该预期输出无需密钥即可覆盖一条真实的已落定命令条目。
+每一条命令条目都变短了,而且这条规则可扩展:新命令的作者写结果时无需知道由哪个界面渲染,任何界面也都不必再去重。代价是分派参数离开了折叠行——命令仍在执行时,行上只有名字和 `执行中…`——以及「不加题头」这条规则是靠评审执行的约定,而非门禁。`/permission` 的文案由 permission 包的命令测试钉住,装配后的行文案由 [seeded-history](../../../../snapshots/web/seeded-history/command-row.expected.md) web 预期输出钉住:由于 `/permission` 完全在 host 上执行,该预期输出无需密钥即可覆盖一条真实的已落定命令条目。

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md
-2026-08-08-bounded-session-persistence-write-batching.md: 6d77ef90276dcf143bb63019f801379b702b43b2
-2026-08-08-bounded-session-persistence-write-batching.zh.md: e773f20ade706e97a5f84d4a4cc15cc9d3b2d836
+2026-08-08-bounded-session-persistence-write-batching.md: fc22a10537ebb9009ab1ae1ec21ca625c9025651
+2026-08-08-bounded-session-persistence-write-batching.zh.md: c2763e157fcfc2e004b14116694054c604fbfb9c

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md

@@ -12,7 +12,7 @@ Dropping chunk events or replacing them with assembled messages would reduce log
 
 
 ### Quantified baseline
 ### Quantified baseline
 
 
-Repository fixtures make the logical volume concrete. Decoding the current packed rows in [`goal-multi-turn-actions`](../../../../apps/web/tests/snapshots/goal-multi-turn-actions/session.jsonl) yields 2,098 events: 2,017 chunks (96.1%). Their unpacked JSONL lines occupy 332,647 of 379,225 event bytes (87.7%), while chunk packing reduces the committed file to 89,176 bytes and 182 storage rows, including 23 packed chunk rows. [`permission-policy-context`](../../../../apps/web/tests/snapshots/permission-policy-context/session.jsonl) yields 813 events: 746 chunks (91.8%) and 118,935 of 184,821 unpacked event bytes (64.4%); its packed file is 84,917 bytes and 123 storage rows, including 14 packed rows. These are tracked deterministic fixtures, not a production workload distribution, but they demonstrate why deleting chunks would reduce logical volume and why the existing packed-row layout already removes much of their JSON envelope cost.
+Repository fixtures make the logical volume concrete. Decoding the current packed rows in [`goal-multi-turn-actions`](../../../../snapshots/web/goal-multi-turn-actions/session.jsonl) yields 2,098 events: 2,017 chunks (96.1%). Their unpacked JSONL lines occupy 332,647 of 379,225 event bytes (87.7%), while chunk packing reduces the committed file to 89,176 bytes and 182 storage rows, including 23 packed chunk rows. [`permission-policy-context`](../../../../snapshots/web/permission-policy-context/session.jsonl) yields 813 events: 746 chunks (91.8%) and 118,935 of 184,821 unpacked event bytes (64.4%); its packed file is 84,917 bytes and 123 storage rows, including 14 packed rows. These are tracked deterministic fixtures, not a production workload distribution, but they demonstrate why deleting chunks would reduce logical volume and why the existing packed-row layout already removes much of their JSON envelope cost.
 
 
 SQLite stores one row per logical event, so those same logical logs would retain 2,098 and 813 event rows respectively; batching does not change those counts. JSONL writes one Zstandard frame and fsync per durable append batch, while SQLite performs one transaction and one session-revision increment per batch. Runtime files do not record former append boundaries, so fixture row counts cannot honestly be presented as fsync or transaction counts.
 SQLite stores one row per logical event, so those same logical logs would retain 2,098 and 813 event rows respectively; batching does not change those counts. JSONL writes one Zstandard frame and fsync per durable append batch, while SQLite performs one transaction and one session-revision increment per batch. Runtime files do not record former append boundaries, so fixture row counts cannot honestly be presented as fsync or transaction counts.
 
 

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.zh.md

@@ -12,7 +12,7 @@ Status: implemented
 
 
 ### 量化基线
 ### 量化基线
 
 
-仓库 fixture(测试前置数据)让逻辑数据量有了具体依据。对当前 [`goal-multi-turn-actions`](../../../../apps/web/tests/snapshots/goal-multi-turn-actions/session.jsonl) 中的打包行进行解码,可得到 2,098 个事件,其中 2,017 个是分片(96.1%)。这些分片解包后的 JSONL 行共 332,647 字节,占全部事件 379,225 字节的 87.7%;分片打包则把仓库中的已提交文件缩小到 89,176 字节和 182 个存储行,其中包括 23 个打包分片行。[`permission-policy-context`](../../../../apps/web/tests/snapshots/permission-policy-context/session.jsonl) 可得到 813 个事件,其中 746 个是分片(91.8%);这些分片解包后的 JSONL 行共 118,935 字节,占全部事件 184,821 字节的 64.4%。其打包文件为 84,917 字节,共 123 个存储行,其中包括 14 个打包行。这些是纳入版本控制的确定性 fixture,不代表生产工作负载分布;但它们说明了删除分片为何会降低逻辑数据量,也说明现有打包行布局已经消除了大量 JSON 包装开销。
+仓库 fixture(测试前置数据)让逻辑数据量有了具体依据。对当前 [`goal-multi-turn-actions`](../../../../snapshots/web/goal-multi-turn-actions/session.jsonl) 中的打包行进行解码,可得到 2,098 个事件,其中 2,017 个是分片(96.1%)。这些分片解包后的 JSONL 行共 332,647 字节,占全部事件 379,225 字节的 87.7%;分片打包则把仓库中的已提交文件缩小到 89,176 字节和 182 个存储行,其中包括 23 个打包分片行。[`permission-policy-context`](../../../../snapshots/web/permission-policy-context/session.jsonl) 可得到 813 个事件,其中 746 个是分片(91.8%);这些分片解包后的 JSONL 行共 118,935 字节,占全部事件 184,821 字节的 64.4%。其打包文件为 84,917 字节,共 123 个存储行,其中包括 14 个打包行。这些是纳入版本控制的确定性 fixture,不代表生产工作负载分布;但它们说明了删除分片为何会降低逻辑数据量,也说明现有打包行布局已经消除了大量 JSON 包装开销。
 
 
 SQLite 每个逻辑事件存储一行,因此同样的逻辑日志会分别保留 2,098 和 813 个事件行;批处理不会改变这些数量。JSONL 每个持久化追加批次会写入一个 Zstandard 帧并执行一次 fsync,SQLite 每个批次会执行一次事务并递增一次会话修订版本。运行时文件不记录原有追加边界,因此不能把 fixture 的存储行数当作 fsync 或事务次数。
 SQLite 每个逻辑事件存储一行,因此同样的逻辑日志会分别保留 2,098 和 813 个事件行;批处理不会改变这些数量。JSONL 每个持久化追加批次会写入一个 Zstandard 帧并执行一次 fsync,SQLite 每个批次会执行一次事务并递增一次会话修订版本。运行时文件不记录原有追加边界,因此不能把 fixture 的存储行数当作 fsync 或事务次数。
 
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-30-web-search-card.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-search-card.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-30-web-search-card.md
-2026-07-30-web-search-card.md: 930a705fc227d7961e9cb1a51b0b04774a62c6a4
-2026-07-30-web-search-card.zh.md: 7771097423bda24c7fe254d5747205c1f06a5474
+2026-07-30-web-search-card.md: 7b16c401a4cac45a218ba0b42743b38171f99d9e
+2026-07-30-web-search-card.zh.md: 4fd653ef519d3ba1cb5e6e3e9c9275b3cc8127bb

+ 1 - 1
.agents/notes/implemented/feature/2026-07-30-web-search-card.md

@@ -56,7 +56,7 @@ Three sites consume the derivation, mirroring the terminal card's placement exac
 
 
 `packages/client/ui-primitives/tests/search-block.client.spec.tsx` pins the component at per-file 100%: both kinds, the folded pre-cap total in the summary, the empty arm, per-file collapse/re-expand without touching neighbours, a file header counting as one capped row alongside its matches, the tail slice restoring its owning file header when the cut falls mid-file, the head/tail cap and its expand control across both shapes and the no-tail and default-cap edges, and the copy control writing the whole structured result on the accepted and refused clipboard paths.
 `packages/client/ui-primitives/tests/search-block.client.spec.tsx` pins the component at per-file 100%: both kinds, the folded pre-cap total in the summary, the empty arm, per-file collapse/re-expand without touching neighbours, a file header counting as one capped row alongside its matches, the tail slice restoring its owning file header when the cut falls mid-file, the head/tail cap and its expand control across both shapes and the no-tail and default-cap edges, and the copy control writing the whole structured result on the accepted and refused clipboard paths.
 
 
-`packages/client/ui-tool/tests/search-card.client.spec.tsx` pins the wiring at every render site: `searchCardModel`'s derivation for both kinds, the truncation signal, the replacement title, the recovery text surfaced only when capped, each null arm (running, no views, generic, terminal, unknown card, an uncompiled `kind`, and a known kind with a missing/malformed shape); the chat row's expand-gated matches and paths bodies through `GenericToolCard` (with the recovery footer) against the non-search args-JSON body; `SearchRow`'s resident card for both kinds, its recovery footer, its fallback body for both an errored search and a settled cardless result, its agreement with the summary row's run state, the replacement-title precedence, and the keyed registration under both `grep` and `glob` with one component; and the details panel's Output section for both kinds (with the recovery footer) against the non-search flattened form. `packages/client/ui-tool/src/*` sits on the coverage exclude list, so this file is written against no gate pressure. `packages/client/connection/src/client/fixture.ts` gains a `grep` turn emitting `kind: 'matches'` (three files, twelve rows over the row cap, `truncated` with a spill-recovery footer, so it exercises the head/tail cap and the recovery footer in the assembled snapshot) and a `glob` turn emitting `kind: 'paths'`, both driving the built-boot snapshot and the live `?fixture` server. `apps/web/tests/search-card.snapshot.ts` is the assembled-output check the repo contract asks for: it boots the real built `client.js` bundles through the keyless fixture transport, opens the fixture session, and pins the grep card's assembled shape — kind, truncation summary, the head/tail slice, and its expand control — under `apps/web/tests/snapshots/search-card/`, so a broken SearchRow registration or a dropped card fails a golden the built-boot smoke (boot-only by contract) cannot.
+`packages/client/ui-tool/tests/search-card.client.spec.tsx` pins the wiring at every render site: `searchCardModel`'s derivation for both kinds, the truncation signal, the replacement title, the recovery text surfaced only when capped, each null arm (running, no views, generic, terminal, unknown card, an uncompiled `kind`, and a known kind with a missing/malformed shape); the chat row's expand-gated matches and paths bodies through `GenericToolCard` (with the recovery footer) against the non-search args-JSON body; `SearchRow`'s resident card for both kinds, its recovery footer, its fallback body for both an errored search and a settled cardless result, its agreement with the summary row's run state, the replacement-title precedence, and the keyed registration under both `grep` and `glob` with one component; and the details panel's Output section for both kinds (with the recovery footer) against the non-search flattened form. `packages/client/ui-tool/src/*` sits on the coverage exclude list, so this file is written against no gate pressure. `packages/client/connection/src/client/fixture.ts` gains a `grep` turn emitting `kind: 'matches'` (three files, twelve rows over the row cap, `truncated` with a spill-recovery footer, so it exercises the head/tail cap and the recovery footer in the assembled snapshot) and a `glob` turn emitting `kind: 'paths'`, both driving the built-boot snapshot and the live `?fixture` server. `apps/web/tests/search-card.snapshot.ts` is the assembled-output check the repo contract asks for: it boots the real built `client.js` bundles through the keyless fixture transport, opens the fixture session, and pins the grep card's assembled shape — kind, truncation summary, the head/tail slice, and its expand control — under `apps/web/tests/goldens/search-card/`, so a broken SearchRow registration or a dropped card fails a golden the built-boot smoke (boot-only by contract) cannot.
 
 
 ## Related
 ## Related
 
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-30-web-search-card.zh.md

@@ -56,7 +56,7 @@ Status: implemented
 
 
 `packages/client/ui-primitives/tests/search-block.client.spec.tsx` 以 per-file 100% 覆盖固定组件:两种 kind、折入摘要的截断前总数、空结果分支、逐文件折叠/再展开且不影响邻居、一个文件头与匹配行一样,在高度上限中单独计为一行、切口落在文件中间时尾部切片恢复其所属文件头、跨两种形态的头/尾上限及其展开控件(含无尾与默认上限的边界),以及复制控件在接受与拒绝的剪贴板路径上写入整个结构化结果。
 `packages/client/ui-primitives/tests/search-block.client.spec.tsx` 以 per-file 100% 覆盖固定组件:两种 kind、折入摘要的截断前总数、空结果分支、逐文件折叠/再展开且不影响邻居、一个文件头与匹配行一样,在高度上限中单独计为一行、切口落在文件中间时尾部切片恢复其所属文件头、跨两种形态的头/尾上限及其展开控件(含无尾与默认上限的边界),以及复制控件在接受与拒绝的剪贴板路径上写入整个结构化结果。
 
 
-`packages/client/ui-tool/tests/search-card.client.spec.tsx` 固定每个渲染点的接线:`searchCardModel` 对两种 kind 的推导、截断信号、替换标题、仅在截断时暴露的恢复文本,以及每个 null 分支(运行中、无视图、generic、terminal、未知卡片、本版本无法编译的 `kind`、以及一个形态缺失/错误的已知 kind);通过 `GenericToolCard` 的展开门控 matches 与 paths body(含恢复脚注),对照非搜索的 args-JSON body;`SearchRow` 对两种 kind 的常驻卡片、它的恢复脚注、它对出错搜索与已结算无卡片结果两者的 fallback body、它与摘要行运行状态的一致、替换标题优先级,以及一个组件在 `grep` 与 `glob` 两个键下的 keyed 注册;以及 details panel 的 Output 段对两种 kind(含恢复脚注),对照非搜索的压平形态。`packages/client/ui-tool/src/*` 在覆盖排除清单上,因此该文件不受 gate 压力。`packages/client/connection/src/client/fixture.ts` 新增一个发出 `kind: 'matches'` 的 `grep` turn(三个文件、十二行超过行内上限、`truncated` 且带溢出恢复脚注,因此在组装快照里同时演练头/尾上限与恢复脚注)与一个发出 `kind: 'paths'` 的 `glob` turn,两者都驱动 built-boot snapshot 与实时 `?fixture` 服务。`apps/web/tests/search-card.snapshot.ts` 是仓库约定要求的组装输出检查:它通过 keyless fixture 传输启动真实构建的 `client.js` bundle,打开 fixture 会话,并把 grep 卡片的组装形态——kind、截断摘要、头/尾切片及其展开控件——固定在 `apps/web/tests/snapshots/search-card/` 下,因此一个损坏的 SearchRow 注册或被丢弃的卡片会让一个 golden 失败,而 built-boot smoke(按约定只测启动)无法捕获它。
+`packages/client/ui-tool/tests/search-card.client.spec.tsx` 固定每个渲染点的接线:`searchCardModel` 对两种 kind 的推导、截断信号、替换标题、仅在截断时暴露的恢复文本,以及每个 null 分支(运行中、无视图、generic、terminal、未知卡片、本版本无法编译的 `kind`、以及一个形态缺失/错误的已知 kind);通过 `GenericToolCard` 的展开门控 matches 与 paths body(含恢复脚注),对照非搜索的 args-JSON body;`SearchRow` 对两种 kind 的常驻卡片、它的恢复脚注、它对出错搜索与已结算无卡片结果两者的 fallback body、它与摘要行运行状态的一致、替换标题优先级,以及一个组件在 `grep` 与 `glob` 两个键下的 keyed 注册;以及 details panel 的 Output 段对两种 kind(含恢复脚注),对照非搜索的压平形态。`packages/client/ui-tool/src/*` 在覆盖排除清单上,因此该文件不受 gate 压力。`packages/client/connection/src/client/fixture.ts` 新增一个发出 `kind: 'matches'` 的 `grep` turn(三个文件、十二行超过行内上限、`truncated` 且带溢出恢复脚注,因此在组装快照里同时演练头/尾上限与恢复脚注)与一个发出 `kind: 'paths'` 的 `glob` turn,两者都驱动 built-boot snapshot 与实时 `?fixture` 服务。`apps/web/tests/search-card.snapshot.ts` 是仓库约定要求的组装输出检查:它通过 keyless fixture 传输启动真实构建的 `client.js` bundle,打开 fixture 会话,并把 grep 卡片的组装形态——kind、截断摘要、头/尾切片及其展开控件——固定在 `apps/web/tests/goldens/search-card/` 下,因此一个损坏的 SearchRow 注册或被丢弃的卡片会让一个 golden 失败,而 built-boot smoke(按约定只测启动)无法捕获它。
 
 
 ## Related
 ## Related
 
 

+ 2 - 2
.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md
-2026-07-30-web-browser-snapshot-ci-gate.md: abb197f2a8e3488e2ae1edc7c2e77cbdcf919556
-2026-07-30-web-browser-snapshot-ci-gate.zh.md: 0239ae6062d2828b8b92683d4b0e7103205756fc
+2026-07-30-web-browser-snapshot-ci-gate.md: e94141070ba283434f49108899f743fab6073ebc
+2026-07-30-web-browser-snapshot-ci-gate.zh.md: 9976034bb0ff8ba0435be41fe9e6bd406267fadb

+ 1 - 1
.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md

@@ -6,7 +6,7 @@ English | [中文](2026-07-30-web-browser-snapshot-ci-gate.zh.md)
 
 
 ## Problem
 ## Problem
 
 
-The [keyless web browser e2e lane](2026-07-24-web-gui-browser-e2e-lane.md) runs only under the local `pnpm run test:web` command, and PR CI does not compare `apps/web/tests/snapshots/**/*.expected.md`. A PR that changes user-visible web output can therefore remain green when its expected outputs are not refreshed; when any later branch explicitly runs `DSH_SNAPSHOT=refresh`, it backfills the earlier change and produces a diff unrelated to that branch. Ordinary local runs already default to read-only replay, so the gap is mandatory enforcement at the PR level, not a ban on writes in refresh mode.
+The [keyless web browser e2e lane](2026-07-24-web-gui-browser-e2e-lane.md) runs only under the local `pnpm run test:web` command, and PR CI does not compare Web expectations under `snapshots/web/` or `apps/web/tests/goldens/`. A PR that changes user-visible web output can therefore remain green when its expected outputs are not refreshed; when any later branch explicitly runs `DSH_SNAPSHOT=refresh`, it backfills the earlier change and produces a diff unrelated to that branch. Ordinary local runs already default to read-only replay, so the gap is mandatory enforcement at the PR level, not a ban on writes in refresh mode.
 
 
 ## Decision
 ## Decision
 
 

+ 1 - 1
.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 
 ## 问题
 ## 问题
 
 
-[无密钥 Web 浏览器 e2e 车道](2026-07-24-web-gui-browser-e2e-lane.zh.md)只由本地 `pnpm run test:web` 运行,PR CI 不比较 `apps/web/tests/snapshots/**/*.expected.md`。因此,改变用户可见 Web 输出的 PR 可以在漏刷预期输出时保持绿色;后来任意分支显式运行 `DSH_SNAPSHOT=refresh`,都会替前序变更补账并产生与本分支无关的 diff。普通本地运行已经默认使用只读 replay,缺口是 PR 级的强制执行,而不是禁止 refresh 写入。
+[无密钥 Web 浏览器 e2e 车道](2026-07-24-web-gui-browser-e2e-lane.zh.md)只由本地 `pnpm run test:web` 运行,PR CI 不比较 `snapshots/web/` 或 `apps/web/tests/goldens/` 下的 Web 预期输出。因此,改变用户可见 Web 输出的 PR 可以在漏刷预期输出时保持绿色;后来任意分支显式运行 `DSH_SNAPSHOT=refresh`,都会替前序变更补账并产生与本分支无关的 diff。普通本地运行已经默认使用只读 replay,缺口是 PR 级的强制执行,而不是禁止 refresh 写入。
 
 
 ## 决策
 ## 决策
 
 

+ 1 - 1
apps/web/tests/access-confirmation.e2e.ts

@@ -13,7 +13,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
 import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/access-confirmation', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/access-confirmation', import.meta.url))
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 
 

+ 1 - 1
apps/web/tests/agent-preset-authoring.e2e.ts

@@ -22,7 +22,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
 import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/agent-preset-authoring', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/agent-preset-authoring', import.meta.url))
 const SECTION_EXPECTED = join(SNAPSHOT_DIR, 'section.expected.md')
 const SECTION_EXPECTED = join(SNAPSHOT_DIR, 'section.expected.md')
 const COPY_DIALOG_EXPECTED = join(SNAPSHOT_DIR, 'copy-dialog.expected.md')
 const COPY_DIALOG_EXPECTED = join(SNAPSHOT_DIR, 'copy-dialog.expected.md')
 const CREATED_EXPECTED = join(SNAPSHOT_DIR, 'created.expected.md')
 const CREATED_EXPECTED = join(SNAPSHOT_DIR, 'created.expected.md')

+ 1 - 1
apps/web/tests/agent-preset-selection.e2e.ts

@@ -24,7 +24,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/agent-preset-selection', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/agent-preset-selection', import.meta.url))
 const HERO_EXPECTED = join(SNAPSHOT_DIR, 'hero.expected.md')
 const HERO_EXPECTED = join(SNAPSHOT_DIR, 'hero.expected.md')
 const MENU_EXPECTED = join(SNAPSHOT_DIR, 'menu.expected.md')
 const MENU_EXPECTED = join(SNAPSHOT_DIR, 'menu.expected.md')
 const HEADER_EXPECTED = join(SNAPSHOT_DIR, 'header.expected.md')
 const HEADER_EXPECTED = join(SNAPSHOT_DIR, 'header.expected.md')

+ 1 - 1
apps/web/tests/approval-composer.e2e.ts

@@ -16,7 +16,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/approval-composer', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/approval-composer', import.meta.url))
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 // The golden covers the stable waiting panel; direct assertions cover its answer.
 // The golden covers the stable waiting panel; direct assertions cover its answer.
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')

+ 2 - 2
apps/web/tests/background-job-list.e2e.ts

@@ -16,8 +16,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const FIXTURE = fileURLToPath(new URL('./snapshots/fresh-round-trip/session.jsonl', import.meta.url))
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/background-job-list', import.meta.url))
+const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/session.jsonl', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/background-job-list', import.meta.url))
 const RUNNING_EXPECTED = join(SNAPSHOT_DIR, 'running.expected.md')
 const RUNNING_EXPECTED = join(SNAPSHOT_DIR, 'running.expected.md')
 const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md')
 const SETTLED_EXPECTED = join(SNAPSHOT_DIR, 'settled.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 1 - 1
apps/web/tests/bash-abort-row.e2e.ts

@@ -14,7 +14,7 @@ import {
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
 const FIXTURE = fileURLToPath(new URL('../../../snapshots/acp/cancel-tool-calls/session.jsonl', import.meta.url))
 const FIXTURE = fileURLToPath(new URL('../../../snapshots/acp/cancel-tool-calls/session.jsonl', import.meta.url))
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/bash-abort-row', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/bash-abort-row', import.meta.url))
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'bash-abort-row-web-e2e'
 const SEED_ID = 'bash-abort-row-web-e2e'

+ 2 - 2
apps/web/tests/code-mode-round.e2e.ts

@@ -13,8 +13,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url))
+const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/code-mode-round/session.jsonl', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/code-mode-round/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 
 
 // Elicits the successful and failed sub-rows this scenario asserts.
 // Elicits the successful and failed sub-rows this scenario asserts.

+ 1 - 1
apps/web/tests/cold-blank-session.e2e.ts

@@ -12,7 +12,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/cold-blank-session', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/cold-blank-session', import.meta.url))
 const SIDEBAR_EXPECTED = join(SNAPSHOT_DIR, 'sidebar.expected.md')
 const SIDEBAR_EXPECTED = join(SNAPSHOT_DIR, 'sidebar.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SESSION_ID = 'cold-blank-session-web-e2e'
 const SESSION_ID = 'cold-blank-session-web-e2e'

+ 1 - 1
apps/web/tests/composer-draft-scroll.e2e.ts

@@ -12,7 +12,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/composer-draft-scroll', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/composer-draft-scroll', import.meta.url))
 /** Scroll geometry is absent from ARIA snapshots, so this scenario records it directly. */
 /** Scroll geometry is absent from ARIA snapshots, so this scenario records it directly. */
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 1 - 1
apps/web/tests/composer-tab-geometry.e2e.ts

@@ -13,7 +13,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/composer-tab-geometry', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/composer-tab-geometry', import.meta.url))
 /** Records platform-neutral distances between the two tabs' card rectangles. */
 /** Records platform-neutral distances between the two tabs' card rectangles. */
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 1 - 1
apps/web/tests/conversation-column-overflow.e2e.ts

@@ -31,7 +31,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/conversation-column-overflow', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/conversation-column-overflow', import.meta.url))
 /**
 /**
  * Committed golden of the one-axis relation at every stop. It records
  * Committed golden of the one-axis relation at every stop. It records
  * relations and booleans, never absolute coordinates: the column width follows
  * relations and booleans, never absolute coordinates: the column width follows

+ 2 - 2
apps/web/tests/cordis-tool-round.e2e.ts

@@ -20,8 +20,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url))
+const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/cordis-tool-round/session.jsonl', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/cordis-tool-round/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const CORDIS_TOOLS = ['cordis_inspect_self', 'cordis_define', 'cordis_run', 'cordis_stop'] as const
 const CORDIS_TOOLS = ['cordis_inspect_self', 'cordis_define', 'cordis_run', 'cordis_stop'] as const
 const PACKAGE_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }'
 const PACKAGE_CODE = 'return { name: "snapshot-noop", apply(ctx) {} }'

+ 2 - 2
apps/web/tests/declared-reasoning.e2e.ts

@@ -18,8 +18,8 @@ import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './s
 
 
 /** Starts the shipped default on this scenario's declared reasoning model. */
 /** Starts the shipped default on this scenario's declared reasoning model. */
 const OVERLAY = fileURLToPath(new URL('./declared-reasoning.overlay.yml', import.meta.url))
 const OVERLAY = fileURLToPath(new URL('./declared-reasoning.overlay.yml', import.meta.url))
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/declared-reasoning', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/declared-reasoning/ui.expected.md', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/declared-reasoning', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('./goldens/declared-reasoning/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 
 
 describe.skipIf(MODE === 'record')('web e2e: declared reasoning efforts reach the composer', () => {
 describe.skipIf(MODE === 'record')('web e2e: declared reasoning efforts reach the composer', () => {

+ 3 - 3
apps/web/tests/details-session-lifecycle.e2e.ts

@@ -14,10 +14,10 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/details-session-lifecycle', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/details-session-lifecycle', import.meta.url))
 const HANDLES_EXPECTED = join(SNAPSHOT_DIR, 'handles.expected.md')
 const HANDLES_EXPECTED = join(SNAPSHOT_DIR, 'handles.expected.md')
-const FIXTURE = fileURLToPath(new URL('./snapshots/lifecycle-chrome/session.jsonl', import.meta.url))
-const SEED_FIXTURE = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
+const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/lifecycle-chrome/session.jsonl', import.meta.url))
+const SEED_FIXTURE = fileURLToPath(new URL('../../../snapshots/web/seeded-history/seed.jsonl', import.meta.url))
 const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.'
 const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.'
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 
 

+ 1 - 1
apps/web/tests/feedback-command.e2e.ts

@@ -20,7 +20,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/feedback-command', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/feedback-command', import.meta.url))
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const ACK_EXPECTED = join(SNAPSHOT_DIR, 'ack.expected.md')
 const ACK_EXPECTED = join(SNAPSHOT_DIR, 'ack.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 1 - 1
apps/web/tests/github-ready-review.e2e.ts

@@ -22,7 +22,7 @@ import { saveFailureShot } from './support.ts'
 
 
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const OVERLAY = fileURLToPath(new URL('../../../examples/web-github-review/cordis.yml', import.meta.url))
 const OVERLAY = fileURLToPath(new URL('../../../examples/web-github-review/cordis.yml', import.meta.url))
-const EXPECTED = fileURLToPath(new URL('./snapshots/github-ready-review/conversation.expected.md', import.meta.url))
+const EXPECTED = fileURLToPath(new URL('./goldens/github-ready-review/conversation.expected.md', import.meta.url))
 const PROVIDER = 'github-webhook-review-test'
 const PROVIDER = 'github-webhook-review-test'
 const MODEL = 'reply'
 const MODEL = 'reply'
 const SECRET = 'github-webhook-review-secret'
 const SECRET = 'github-webhook-review-secret'

+ 1 - 1
apps/web/tests/goal-bar.e2e.ts

@@ -14,7 +14,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/goal-bar', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/goal-bar', import.meta.url))
 const ACTIVE_EXPECTED = join(SNAPSHOT_DIR, 'active.expected.md')
 const ACTIVE_EXPECTED = join(SNAPSHOT_DIR, 'active.expected.md')
 const OVERLAY = fileURLToPath(new URL('./goal-bar.overlay.yml', import.meta.url))
 const OVERLAY = fileURLToPath(new URL('./goal-bar.overlay.yml', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 2 - 2
apps/web/tests/goal-command-presentation.e2e.ts

@@ -14,9 +14,9 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/goal-command-presentation', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/goal-command-presentation', import.meta.url))
 const UI_EXPECTED = fileURLToPath(new URL(
 const UI_EXPECTED = fileURLToPath(new URL(
-  './snapshots/goal-command-presentation/ui.expected.md', import.meta.url,
+  './goldens/goal-command-presentation/ui.expected.md', import.meta.url,
 ))
 ))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 
 

+ 1 - 1
apps/web/tests/goal-multi-turn-actions.e2e.ts

@@ -16,7 +16,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/goal-multi-turn-actions', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/goal-multi-turn-actions', import.meta.url))
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const OVERRIDE = join(SNAPSHOT_DIR, 'replay.override.json')
 const OVERRIDE = join(SNAPSHOT_DIR, 'replay.override.json')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')

+ 0 - 0
apps/web/tests/snapshots/access-confirmation/ui.expected.md → apps/web/tests/goldens/access-confirmation/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-authoring/copy-dialog.expected.md → apps/web/tests/goldens/agent-preset-authoring/copy-dialog.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-authoring/created.expected.md → apps/web/tests/goldens/agent-preset-authoring/created.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-authoring/damaged.expected.md → apps/web/tests/goldens/agent-preset-authoring/damaged.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-authoring/section.expected.md → apps/web/tests/goldens/agent-preset-authoring/section.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-selection/header.expected.md → apps/web/tests/goldens/agent-preset-selection/header.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-selection/hero.expected.md → apps/web/tests/goldens/agent-preset-selection/hero.expected.md


+ 0 - 0
apps/web/tests/snapshots/agent-preset-selection/menu.expected.md → apps/web/tests/goldens/agent-preset-selection/menu.expected.md


+ 0 - 0
apps/web/tests/snapshots/cold-blank-session/sidebar.expected.md → apps/web/tests/goldens/cold-blank-session/sidebar.expected.md


+ 0 - 0
apps/web/tests/snapshots/composer-draft-scroll/geometry.expected.md → apps/web/tests/goldens/composer-draft-scroll/geometry.expected.md


+ 0 - 0
apps/web/tests/snapshots/composer-tab-geometry/geometry.expected.md → apps/web/tests/goldens/composer-tab-geometry/geometry.expected.md


+ 0 - 0
apps/web/tests/snapshots/conversation-column-overflow/geometry.expected.md → apps/web/tests/goldens/conversation-column-overflow/geometry.expected.md


+ 0 - 0
apps/web/tests/snapshots/declared-reasoning/ui.expected.md → apps/web/tests/goldens/declared-reasoning/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/github-ready-review/conversation.expected.md → apps/web/tests/goldens/github-ready-review/conversation.expected.md


+ 0 - 0
apps/web/tests/snapshots/goal-bar/active.expected.md → apps/web/tests/goldens/goal-bar/active.expected.md


+ 0 - 0
apps/web/tests/snapshots/goal-command-presentation/ui.expected.md → apps/web/tests/goldens/goal-command-presentation/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/home-path-tilde/workspace-hover.expected.txt → apps/web/tests/goldens/home-path-tilde/workspace-hover.expected.txt


+ 0 - 0
apps/web/tests/snapshots/markdown-cjk-strong/ui.expected.md → apps/web/tests/goldens/markdown-cjk-strong/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/markdown-images/ui.expected.md → apps/web/tests/goldens/markdown-images/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/markdown-inline-code-links/ui.expected.md → apps/web/tests/goldens/markdown-inline-code-links/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/markdown-wide-table/geometry.expected.md → apps/web/tests/goldens/markdown-wide-table/geometry.expected.md


+ 0 - 0
apps/web/tests/snapshots/math-rendering/ui.expected.md → apps/web/tests/goldens/math-rendering/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/max-tokens-notice/history-turn.expected.txt → apps/web/tests/goldens/max-tokens-notice/history-turn.expected.txt


+ 0 - 0
apps/web/tests/snapshots/models-settings/configured.expected.md → apps/web/tests/goldens/models-settings/configured.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/declared-edit.expected.md → apps/web/tests/goldens/models-settings/declared-edit.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/declared.expected.md → apps/web/tests/goldens/models-settings/declared.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/delete.expected.md → apps/web/tests/goldens/models-settings/delete.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/empty.expected.md → apps/web/tests/goldens/models-settings/empty.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/model-picker.expected.md → apps/web/tests/goldens/models-settings/model-picker.expected.md


+ 0 - 0
apps/web/tests/snapshots/models-settings/native-delete.expected.md → apps/web/tests/goldens/models-settings/native-delete.expected.md


+ 0 - 0
apps/web/tests/snapshots/onboarding-deepseek-config/missing.expected.md → apps/web/tests/goldens/onboarding-deepseek-config/missing.expected.md


+ 0 - 0
apps/web/tests/snapshots/onboarding-deepseek-config/models.expected.md → apps/web/tests/goldens/onboarding-deepseek-config/models.expected.md


+ 0 - 0
apps/web/tests/snapshots/onboarding-deepseek-config/welcome.expected.md → apps/web/tests/goldens/onboarding-deepseek-config/welcome.expected.md


+ 0 - 0
apps/web/tests/snapshots/onboarding-usable-provider/dismissed.expected.md → apps/web/tests/goldens/onboarding-usable-provider/dismissed.expected.md


+ 0 - 0
apps/web/tests/snapshots/plugin-config/section.expected.md → apps/web/tests/goldens/plugin-config/section.expected.md


+ 0 - 0
apps/web/tests/snapshots/reference-composer/caret-edits.expected.md → apps/web/tests/goldens/reference-composer/caret-edits.expected.md


+ 0 - 0
apps/web/tests/snapshots/reference-composer/menu.expected.md → apps/web/tests/goldens/reference-composer/menu.expected.md


+ 0 - 0
apps/web/tests/snapshots/reference-composer/order.expected.md → apps/web/tests/goldens/reference-composer/order.expected.md


+ 0 - 0
apps/web/tests/snapshots/schedule-after/at-conversation.expected.md → apps/web/tests/goldens/schedule-after/at-conversation.expected.md


+ 0 - 0
apps/web/tests/snapshots/schedule-after/conversation.expected.md → apps/web/tests/goldens/schedule-after/conversation.expected.md


+ 0 - 0
apps/web/tests/snapshots/schedule-after/every-conversation.expected.md → apps/web/tests/goldens/schedule-after/every-conversation.expected.md


+ 0 - 0
apps/web/tests/snapshots/search-card/grep-card.expected.txt → apps/web/tests/goldens/search-card/grep-card.expected.txt


+ 0 - 0
apps/web/tests/snapshots/settings-chrome/dialog-en.expected.md → apps/web/tests/goldens/settings-chrome/dialog-en.expected.md


+ 0 - 0
apps/web/tests/snapshots/settings-chrome/dialog.expected.md → apps/web/tests/goldens/settings-chrome/dialog.expected.md


+ 0 - 0
apps/web/tests/snapshots/settings-chrome/plugins.expected.md → apps/web/tests/goldens/settings-chrome/plugins.expected.md


+ 0 - 0
apps/web/tests/snapshots/sidebar-subagent-activity/owner-running.expected.md → apps/web/tests/goldens/sidebar-subagent-activity/owner-running.expected.md


+ 0 - 0
apps/web/tests/snapshots/skill-invocation-policy/menu.expected.md → apps/web/tests/goldens/skill-invocation-policy/menu.expected.md


+ 0 - 0
apps/web/tests/snapshots/skill-user-invoke/ui.expected.md → apps/web/tests/goldens/skill-user-invoke/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/stats-paged-history/ui.expected.md → apps/web/tests/goldens/stats-paged-history/ui.expected.md


+ 0 - 0
apps/web/tests/snapshots/steer-all/mid-steer.expected.md → apps/web/tests/goldens/steer-all/mid-steer.expected.md


+ 0 - 0
apps/web/tests/snapshots/steer-all/replay.override.json → apps/web/tests/goldens/steer-all/replay.override.json


+ 0 - 0
apps/web/tests/snapshots/steer-all/settled.expected.md → apps/web/tests/goldens/steer-all/settled.expected.md


+ 0 - 0
apps/web/tests/snapshots/todo-row/parallel-plan.expected.txt → apps/web/tests/goldens/todo-row/parallel-plan.expected.txt


+ 0 - 0
apps/web/tests/snapshots/trajectory-virtualization/load-more.expected.md → apps/web/tests/goldens/trajectory-virtualization/load-more.expected.md


+ 0 - 0
apps/web/tests/snapshots/web-runtime-context/file-reference-prompt.expected.md → apps/web/tests/goldens/web-runtime-context/file-reference-prompt.expected.md


+ 0 - 0
apps/web/tests/snapshots/web-runtime-context/web-surface-prompt.expected.md → apps/web/tests/goldens/web-runtime-context/web-surface-prompt.expected.md


+ 1 - 1
apps/web/tests/home-path-tilde.snapshot.ts

@@ -8,7 +8,7 @@ import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
 import { describe, expect, it } from 'vitest'
 import { describe, expect, it } from 'vitest'
 import { installAssembledBootEnv, mountAssembledApp, REFRESHING_GOLDEN } from './assembled-boot.ts'
 import { installAssembledBootEnv, mountAssembledApp, REFRESHING_GOLDEN } from './assembled-boot.ts'
 
 
-const EXPECTED = join(process.cwd(), 'apps/web/tests/snapshots/home-path-tilde/workspace-hover.expected.txt')
+const EXPECTED = join(process.cwd(), 'apps/web/tests/goldens/home-path-tilde/workspace-hover.expected.txt')
 
 
 installAssembledBootEnv()
 installAssembledBootEnv()
 
 

+ 1 - 1
apps/web/tests/lifecycle-chrome.e2e.ts

@@ -23,7 +23,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/lifecycle-chrome', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/lifecycle-chrome', import.meta.url))
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const REPLAY_OVERRIDE = join(SNAPSHOT_DIR, 'replay.override.json')
 const REPLAY_OVERRIDE = join(SNAPSHOT_DIR, 'replay.override.json')
 const HERO_EXPECTED = join(SNAPSHOT_DIR, 'hero.expected.md')
 const HERO_EXPECTED = join(SNAPSHOT_DIR, 'hero.expected.md')

+ 1 - 1
apps/web/tests/live-interactions.e2e.ts

@@ -28,7 +28,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/live-interactions', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/live-interactions', import.meta.url))
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 // One golden pins the empty mid-turn loading state, one pins the sendable draft
 // One golden pins the empty mid-turn loading state, one pins the sendable draft
 // state, and the other four capture what remains after cancel, after a
 // state, and the other four capture what remains after cancel, after a

+ 2 - 2
apps/web/tests/markdown-cjk-strong.e2e.ts

@@ -17,8 +17,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/markdown-cjk-strong', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/markdown-cjk-strong/ui.expected.md', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/markdown-cjk-strong', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('./goldens/markdown-cjk-strong/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'markdown-cjk-strong-web-e2e'
 const SEED_ID = 'markdown-cjk-strong-web-e2e'
 const DONE = 'CJK_STRONG_DONE'
 const DONE = 'CJK_STRONG_DONE'

+ 2 - 2
apps/web/tests/markdown-images.e2e.ts

@@ -26,8 +26,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/markdown-images', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/markdown-images/ui.expected.md', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/markdown-images', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('./goldens/markdown-images/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'markdown-images-web-e2e'
 const SEED_ID = 'markdown-images-web-e2e'
 const REMOTE_ALT = 'Remote test image'
 const REMOTE_ALT = 'Remote test image'

+ 2 - 2
apps/web/tests/markdown-inline-code-links.e2e.ts

@@ -17,8 +17,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/markdown-inline-code-links', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/markdown-inline-code-links/ui.expected.md', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/markdown-inline-code-links', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('./goldens/markdown-inline-code-links/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'markdown-inline-code-links-web-e2e'
 const SEED_ID = 'markdown-inline-code-links-web-e2e'
 const DONE = 'INLINE_CODE_LINK_DONE'
 const DONE = 'INLINE_CODE_LINK_DONE'

+ 2 - 2
apps/web/tests/markdown-wide-table.e2e.ts

@@ -40,9 +40,9 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/markdown-wide-table', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/markdown-wide-table', import.meta.url))
 const GEOMETRY_EXPECTED = fileURLToPath(
 const GEOMETRY_EXPECTED = fileURLToPath(
-  new URL('./snapshots/markdown-wide-table/geometry.expected.md', import.meta.url),
+  new URL('./goldens/markdown-wide-table/geometry.expected.md', import.meta.url),
 )
 )
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'markdown-wide-table-web-e2e'
 const SEED_ID = 'markdown-wide-table-web-e2e'

+ 2 - 2
apps/web/tests/math-rendering.e2e.ts

@@ -17,8 +17,8 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/math-rendering', import.meta.url))
-const UI_EXPECTED = fileURLToPath(new URL('./snapshots/math-rendering/ui.expected.md', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('./goldens/math-rendering', import.meta.url))
+const UI_EXPECTED = fileURLToPath(new URL('./goldens/math-rendering/ui.expected.md', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'math-rendering-web-e2e'
 const SEED_ID = 'math-rendering-web-e2e'
 const DONE = 'MATH_RENDERING_DONE'
 const DONE = 'MATH_RENDERING_DONE'

+ 1 - 1
apps/web/tests/max-tokens-notice.snapshot.ts

@@ -15,7 +15,7 @@ import { fireEvent, screen, waitFor, within } from '@testing-library/react'
 import { describe, expect, it } from 'vitest'
 import { describe, expect, it } from 'vitest'
 import { hasClass, installAssembledBootEnv, mountAssembledApp, REFRESHING_GOLDEN } from './assembled-boot.ts'
 import { hasClass, installAssembledBootEnv, mountAssembledApp, REFRESHING_GOLDEN } from './assembled-boot.ts'
 
 
-const EXPECTED = join(process.cwd(), 'apps/web/tests/snapshots/max-tokens-notice/history-turn.expected.txt')
+const EXPECTED = join(process.cwd(), 'apps/web/tests/goldens/max-tokens-notice/history-turn.expected.txt')
 
 
 installAssembledBootEnv()
 installAssembledBootEnv()
 
 

+ 2 - 2
apps/web/tests/message-actions.e2e.ts

@@ -15,10 +15,10 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-actions', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/message-actions', import.meta.url))
 // Borrowed read-only: this scenario needs any settled user+assistant pair, not
 // Borrowed read-only: this scenario needs any settled user+assistant pair, not
 // a new recording (workspace-management / sidebar-scrollbar pattern).
 // a new recording (workspace-management / sidebar-scrollbar pattern).
-const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
+const SEED = fileURLToPath(new URL('../../../snapshots/web/seeded-history/seed.jsonl', import.meta.url))
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
 const FORK_EXPECTED = join(SNAPSHOT_DIR, 'fork.expected.md')
 const FORK_EXPECTED = join(SNAPSHOT_DIR, 'fork.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()

+ 2 - 2
apps/web/tests/message-feedback-layout.e2e.ts

@@ -33,7 +33,7 @@ import {
 } from './scaffold.ts'
 } from './scaffold.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-feedback-layout', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/message-feedback-layout', import.meta.url))
 /**
 /**
  * Committed golden of the popover relations at every stop. Booleans and counts
  * Committed golden of the popover relations at every stop. Booleans and counts
  * only, never absolute coordinates.
  * only, never absolute coordinates.
@@ -41,7 +41,7 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-feedback-layout'
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 /** Borrowed read-only: this scenario needs any settled assistant message to rate. */
 /** Borrowed read-only: this scenario needs any settled assistant message to rate. */
-const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
+const SEED = fileURLToPath(new URL('../../../snapshots/web/seeded-history/seed.jsonl', import.meta.url))
 const SEED_ID = 'message-feedback-layout-e2e'
 const SEED_ID = 'message-feedback-layout-e2e'
 /** Viewport widths from full-screen desktop down to a narrow window. */
 /** Viewport widths from full-screen desktop down to a narrow window. */
 const WIDTHS = [1680, 1280, 1024, 900, 700, 600]
 const WIDTHS = [1680, 1280, 1024, 900, 700, 600]

+ 1 - 1
apps/web/tests/message-feedback-protocol.snapshot.ts

@@ -10,7 +10,7 @@ import {
   type WebScaffold,
   type WebScaffold,
 } from './scaffold.ts'
 } from './scaffold.ts'
 
 
-const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-feedback-protocol', import.meta.url))
+const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/message-feedback-protocol', import.meta.url))
 const SESSION_FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const SESSION_FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
 const PROTOCOL_EXPECTED = join(SNAPSHOT_DIR, 'protocol.expected.json')
 const PROTOCOL_EXPECTED = join(SNAPSHOT_DIR, 'protocol.expected.json')
 const SESSION_ID = 'message-feedback-protocol'
 const SESSION_ID = 'message-feedback-protocol'

+ 1 - 1
apps/web/tests/message-feedback.e2e.ts

@@ -15,7 +15,7 @@ import { newEnglishPage, saveFailureShot } from './support.ts'
 
 
 // Borrowed read-only: this scenario needs any settled assistant message to
 // Borrowed read-only: this scenario needs any settled assistant message to
 // address, not a new recording (message-actions / sidebar-scrollbar pattern).
 // address, not a new recording (message-actions / sidebar-scrollbar pattern).
-const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
+const SEED = fileURLToPath(new URL('../../../snapshots/web/seeded-history/seed.jsonl', import.meta.url))
 const MODE = webSnapshotMode()
 const MODE = webSnapshotMode()
 const SEED_ID = 'message-feedback-web-e2e'
 const SEED_ID = 'message-feedback-web-e2e'
 const NOTE = 'Read both files before answering.'
 const NOTE = 'Read both files before answering.'

Някои файлове не бяха показани, защото твърде много файлове са промени