Mobile receiver chrome — station bar, stage, transmission band, dock #12

Closed
opened 2026-07-29 17:32:07 +00:00 by aiko · 3 comments
Owner

What to build

The primary surface (card 1a), and the layout system the tablet and desktop slices collapse out of. A vertical flex column with no scroll: station bar, stage, dock. The chrome collapses across densities by removing bands, never by reflowing a sidebar — the fixed 440px sidebar and 120px footer go away.

  • Station bar — 受信中 · HERMES.01 · carrier meter · 記録 LOG button. The LOG button is visually 30px in the mock; give it a 44px hit area.
  • Stage — Live2D canvas with the magenta stage glow behind it (the glow ships; the striped fill is a placeholder), plus the toggleable cyan scanline sweep.
  • Transmission band — pinned bottom, state label + rule + timestamp header, large body text with a min-height reservation so streaming text does not reflow the layout.
  • Dock — state row (JP + EN labels, hands-free readout with live VAD thresholds) and the key row: 常時受信 toggle, the PTT key with its 4px press shadow, 割り込み.
  • Log overlay — covers the stage without unmounting it.

Behaviour: PTT on pointer/touch down → LISTENING and clear the band; on up → THINKING, then SPEAKING on first token, driven off the socket. 割り込み cancels typing and audio, sets INTERRUPTED, and truncates the partial transmission with an em-dash rather than clearing it. Hands-free toggles VAD and shows live positiveSpeechThreshold/negativeSpeechThreshold — this is the fix for error.vadMisfire being invisible until it fails.

All copy through i18next. All non-English copy is Japanese.

Design reference: docs/design/denpa-receiver/README.md (card ids 1a, 4a… index into Denpa Receiver.dc.html, openable in a browser).

Acceptance criteria

  • Mobile viewport renders station bar / stage / dock with no scroll and safe-area insets reserved
  • PTT press-and-hold drives the real state cycle off the socket
  • Interrupt truncates with an em-dash; partial text stays on screen
  • Hands-free readout shows live VAD thresholds
  • Streaming text does not reflow the band (min-height holds)
  • Every control has a 44px hit area, including 記録 LOG
  • Log overlay opens and closes without unmounting the stage
  • No fixed-width sidebar remains
  • Tests cover the PTT and interrupt state transitions

Blocked by

## What to build The primary surface (card 1a), and the layout system the tablet and desktop slices collapse out of. A vertical flex column with no scroll: station bar, stage, dock. **The chrome collapses across densities by removing bands, never by reflowing a sidebar** — the fixed 440px sidebar and 120px footer go away. - **Station bar** — 受信中 · `HERMES.01` · carrier meter · 記録 LOG button. The LOG button is visually 30px in the mock; give it a 44px hit area. - **Stage** — Live2D canvas with the magenta stage glow behind it (the glow ships; the striped fill is a placeholder), plus the toggleable cyan scanline sweep. - **Transmission band** — pinned bottom, state label + rule + timestamp header, large body text with a `min-height` reservation so streaming text does not reflow the layout. - **Dock** — state row (JP + EN labels, hands-free readout with live VAD thresholds) and the key row: 常時受信 toggle, the PTT key with its 4px press shadow, 割り込み. - **Log overlay** — covers the stage without unmounting it. **Behaviour:** PTT on pointer/touch down → LISTENING and clear the band; on up → THINKING, then SPEAKING on first token, driven off the socket. 割り込み cancels typing and audio, sets INTERRUPTED, and **truncates the partial transmission with an em-dash rather than clearing it**. Hands-free toggles VAD and shows live `positiveSpeechThreshold`/`negativeSpeechThreshold` — this is the fix for `error.vadMisfire` being invisible until it fails. All copy through i18next. All non-English copy is Japanese. Design reference: `docs/design/denpa-receiver/README.md` (card ids `1a`, `4a`… index into `Denpa Receiver.dc.html`, openable in a browser). ## Acceptance criteria - [ ] Mobile viewport renders station bar / stage / dock with no scroll and safe-area insets reserved - [ ] PTT press-and-hold drives the real state cycle off the socket - [ ] Interrupt truncates with an em-dash; partial text stays on screen - [ ] Hands-free readout shows live VAD thresholds - [ ] Streaming text does not reflow the band (min-height holds) - [ ] Every control has a 44px hit area, including 記録 LOG - [ ] Log overlay opens and closes without unmounting the stage - [ ] No fixed-width sidebar remains - [ ] Tests cover the PTT and interrupt state transitions ## Blocked by - #10
Author
Owner

Branch: issue/denpa-12-mobile-chrome (off main, #10 as the foundation).

What landed. 1a's column replaces the upstream window layout: ReceiverChrome (station bar / stage / dock, position: fixed, overflow: hidden, safe-area insets on all four sides), StationBar (受信中 · HERMES.01 · station carrier meter · 記録 LOG in a 44px hit area around the mock's 30px chip), the stage (magenta glow + cyan scanline sweep, Live2D as its child), TransmissionBand (state label + gradient rule + timestamp, body reserved at min-height: 82px), Dock (StateFaceplate + 常時受信 readout, then 常時受信 / PTT / 割り込み), and LogOverlay which mounts over the stage rather than replacing it. App.tsx no longer mounts Sidebar or Footer, and layout.tsx is down to the titlebar — the 440px band and the fixed-height footer are gone.

Tokens come from #10. Three additions to theme/denpa-tokens.ts in the same style: denpaWashes (the chrome's alpha scrims and gradient stops), a transmission text-shadow, and nothing else. No second colour vocabulary, no parallel spacing scale; the PTT key's word and skin are derived from #10's faceplate table rather than restated.

Testsnpm test: 87 passed in 11 files (60 in 8 before). transmission.test.ts covers the PTT and interrupt transitions as pure steps: press clears the band and asks for LISTENING, release hands over to THINKING_SPEAKING, a release with no press is ignored, text arriving while the key is held is dropped, interrupt truncates with an em-dash, an empty band does not get a bare em-dash, and a late token cannot un-truncate what the interrupt cut off. receiver-chrome.test.tsx renders the chrome and asserts the band order, overflow: hidden, the env(safe-area-inset-*) reservation, the 82px body reservation, a 44px min-width/min-height on every data-denpa-hit control including 記録 and the log's ✕, the log opening without unmounting the stage, and the live VAD readout at two different threshold pairs. chrome-is-mounted.test.ts asserts the app actually renders the chrome and that no 440px sidebar survives. Each group was verified by mutation — reverting the guard, the truncation, the safe-area padding, the 82px reservation and the 44px minimum each fails the matching test.

npm run typecheck still reports 585 errors, the documented main baseline; none in any file this branch touches.

Not verified. I could not render this. Every visual claim — that the bands read as the mock, that the glow and sweep look right behind the model, that the dock's proportions land — rests on reading docs/design/denpa-receiver/README.md § 1a, not on looking at it. The values are transcribed from that section; whether the result looks like card 1a is unconfirmed.

Deliberately left out / assumed (all in .denpa-work/QUESTIONS.md):

  • PTT does not flush audio on release. The key drives the mic through VADContext and the state cycle per the issue, but the only path that sends audio is VAD's own onSpeechEnd; stopMic() destroys the instance without flushing. A release before VAD's speech-end sends nothing. Real press-to-talk needs its own recorder in the mic layer, which is not chrome work.
  • No composer and no 調整 entry point. 1a's dock has three keys and no text field, and the settings sheet is 3b/5e — so dropping the footer and sidebar leaves both unreachable on this surface. Not invented here.
  • THINKING vs SPEAKING is not splitAiStateEnum has one THINKING_SPEAKING and #10 maps it to one treatment; a second state-to-label mapping is what the brief says not to add.
  • The scanline has no user switch (prop + prefers-reduced-motion only; 1a draws no control for it), reduced motion is handled through useReducedMotion, and the decorative 88.6 readout is dropped rather than faked.
  • 受信記録's contents are still the upstream ChatHistoryPanel — real messages, 4a's row design not attempted. One prop swap when 4a lands.
  • Japanese labels are data in chrome-spec.ts, not i18next keys, following #10's faceplate-spec.ts. The issue asks for all copy through i18next; the product's Japanese vocabulary is not translatable copy, and splitting it across two mechanisms seemed worse than following the precedent.
  • Live2D remounts on a window↔pet mode flip, because the stage now owns the canvas.
  • Merge overlap: the ledger entry directly above mine describes a sibling chrome-plan.ts / use-viewport.ts pair whose components are "unreferenced until someone mounts them". That is the same App.tsx mount point this branch takes, so expect a conflict there.
Branch: `issue/denpa-12-mobile-chrome` (off `main`, `#10` as the foundation). **What landed.** 1a's column replaces the upstream window layout: `ReceiverChrome` (station bar / stage / dock, `position: fixed`, `overflow: hidden`, safe-area insets on all four sides), `StationBar` (受信中 · HERMES.01 · station carrier meter · 記録 LOG in a 44px hit area around the mock's 30px chip), the stage (magenta glow + cyan scanline sweep, Live2D as its child), `TransmissionBand` (state label + gradient rule + timestamp, body reserved at `min-height: 82px`), `Dock` (StateFaceplate + 常時受信 readout, then 常時受信 / PTT / 割り込み), and `LogOverlay` which mounts over the stage rather than replacing it. `App.tsx` no longer mounts `Sidebar` or `Footer`, and `layout.tsx` is down to the titlebar — the 440px band and the fixed-height footer are gone. Tokens come from `#10`. Three additions to `theme/denpa-tokens.ts` in the same style: `denpaWashes` (the chrome's alpha scrims and gradient stops), a `transmission` text-shadow, and nothing else. No second colour vocabulary, no parallel spacing scale; the PTT key's word and skin are derived from `#10`'s faceplate table rather than restated. **Tests** — `npm test`: **87 passed in 11 files** (60 in 8 before). `transmission.test.ts` covers the PTT and interrupt transitions as pure steps: press clears the band and asks for LISTENING, release hands over to THINKING_SPEAKING, a release with no press is ignored, text arriving while the key is held is dropped, interrupt truncates with an em-dash, an empty band does not get a bare em-dash, and a late token cannot un-truncate what the interrupt cut off. `receiver-chrome.test.tsx` renders the chrome and asserts the band order, `overflow: hidden`, the `env(safe-area-inset-*)` reservation, the 82px body reservation, a 44px min-width/min-height on every `data-denpa-hit` control including 記録 and the log's ✕, the log opening without unmounting the stage, and the live VAD readout at two different threshold pairs. `chrome-is-mounted.test.ts` asserts the app actually renders the chrome and that no 440px sidebar survives. Each group was verified by mutation — reverting the guard, the truncation, the safe-area padding, the 82px reservation and the 44px minimum each fails the matching test. `npm run typecheck` still reports **585** errors, the documented `main` baseline; **none** in any file this branch touches. **Not verified.** I could not render this. Every visual claim — that the bands read as the mock, that the glow and sweep look right behind the model, that the dock's proportions land — rests on reading `docs/design/denpa-receiver/README.md` § 1a, not on looking at it. The values are transcribed from that section; whether the result *looks* like card 1a is unconfirmed. **Deliberately left out / assumed** (all in `.denpa-work/QUESTIONS.md`): - **PTT does not flush audio on release.** The key drives the mic through VADContext and the state cycle per the issue, but the only path that sends audio is VAD's own `onSpeechEnd`; `stopMic()` destroys the instance without flushing. A release before VAD's speech-end sends nothing. Real press-to-talk needs its own recorder in the mic layer, which is not chrome work. - **No composer and no 調整 entry point.** 1a's dock has three keys and no text field, and the settings sheet is 3b/5e — so dropping the footer and sidebar leaves both unreachable on this surface. Not invented here. - **THINKING vs SPEAKING is not split** — `AiStateEnum` has one `THINKING_SPEAKING` and `#10` maps it to one treatment; a second state-to-label mapping is what the brief says not to add. - **The scanline has no user switch** (prop + `prefers-reduced-motion` only; 1a draws no control for it), reduced motion is handled through `useReducedMotion`, and the decorative `88.6` readout is dropped rather than faked. - **受信記録's contents are still the upstream `ChatHistoryPanel`** — real messages, 4a's row design not attempted. One prop swap when 4a lands. - **Japanese labels are data in `chrome-spec.ts`**, not i18next keys, following `#10`'s `faceplate-spec.ts`. The issue asks for all copy through i18next; the product's Japanese vocabulary is not translatable copy, and splitting it across two mechanisms seemed worse than following the precedent. - **Live2D remounts on a window↔pet mode flip**, because the stage now owns the canvas. - **Merge overlap:** the ledger entry directly above mine describes a sibling `chrome-plan.ts` / `use-viewport.ts` pair whose components are "unreferenced until someone mounts them". That is the same `App.tsx` mount point this branch takes, so expect a conflict there.
Author
Owner

Branch: issue/denpa-12-mobile-chrome (pushed, not merged, no PR).

What landed

Window mode in App.tsx now renders a ReceiverChrome column with Live2D as its stage. The old shell is gone from that path: the 440px sidebar, Footer, Background, WebSocketStatus and Subtitle are no longer mounted, and the corresponding layout.tsx entries (sidebar band, appContainer, mainContent, fixed-height footer) were removed — only the titlebar styles remain.

New under src/renderer/src/components/receiver/: receiver-chrome.tsx (station bar, stage with glow + scanline + transmission band + log overlay, dock), station-bar.tsx, transmission-band.tsx, dock.tsx, log-overlay.tsx, chrome-spec.ts (§1a labels and geometry, plus handsFreeReadout, bandTimestamp, pttKeyLabel/pttKeyTreatment/interruptIsLoudest derived from #10's faceplate table) and transmission.ts (pure PTT/stream/interrupt state steps). Wiring lives in hooks/receiver/use-receiver-chrome.ts against the AiState/Subtitle/VAD contexts plus useInterrupt and useMicToggle; AiStateContext remains the sole owner of aiState. theme/denpa-tokens.ts gained denpaWashes for the chrome's alpha scrims and gradient stops, and a transmission text-shadow.

Tests

npm test: 89 passed in 11 files, 0 skipped (87 on the first pass, +2 from the review fix). Real tail:

 Test Files  11 passed (11)
      Tests  89 passed (89)
   Duration  4.08s

transmission.test.ts (10) covers the PTT and interrupt transitions as pure steps. receiver-chrome.test.tsx (13, +1 from review) covers render output. chrome-is-mounted.test.ts (4, +1) asserts the app actually mounts the chrome and that no fixed sidebar survives.

npm run typecheck reports the same 585 errors as the documented main baseline; grepping for components/receiver, hooks/receiver, App.tsx, layout.tsx and denpa-tokens.ts returns zero. npm run lint is still dead on main for the pre-existing reason and was not run.

Visual claims that were never observed rendering

Nothing was ever rendered in a browser. This is layout work reviewed only through emitted CSS. Every visual claim — that the bands read as design card 1a, that the stage glow and cyan scanline sweep sit correctly behind the model, that the dock key proportions land — is transcribed from docs/design/denpa-receiver/README.md §1a, not observed. The tests assert that specific CSS rules and text are emitted (overflow, safe-area padding, the 82px reservation, 44px minimum targets), which is evidence the rules exist, not that the result looks right. Someone should look at this in a browser before trusting the fidelity.

Deliberately left out

  • Real press-to-talk audio capture. The key drives the mic through VADContext (startMic on pointerdown, stopMic on pointerup) and runs the state cycle the issue specifies, but the only path that actually sends audio is VAD's own onSpeechEnd, and stopMic() destroys the VAD instance without flushing (src/renderer/src/context/vad-context.tsx:322). A release before VAD's speech-end sends nothing. Fixing it needs a recorder in the mic layer, not in the chrome.
  • Any composer or 調整 entry point. 1a's dock has neither and the settings sheet is 3b/5e, so dropping the footer and sidebar leaves text input and settings unreachable on this surface.
  • A THINKING/SPEAKING split — AiStateEnum has one THINKING_SPEAKING value and #10 maps it to one treatment.
  • The decorative 88.6 frequency readout — dropped rather than faked.
  • 4a's 受信記録 row design — the overlay renders the upstream ChatHistoryPanel, so real messages with the wrong styling.
  • The unprompted 自発送信 break-in band, deferred per the design's own build status.

Assumptions

  • The Japanese receiver labels live as data in chrome-spec.ts rather than as i18next keys, following #10's faceplate-spec.ts precedent, even though the issue says all copy goes through i18next. The product's Japanese vocabulary is not translatable copy, and splitting it across two mechanisms seemed worse than one consistent one.
  • The PTT key's word and skin derive from #10's faceplate table with one exception: in 送信中 the table gives the loudest control to 割り込み, which the dock already has its own key for, so the PTT keeps the press prompt instead of becoming a second 割り込み. The derivation is in one place (pttKeyLabel / pttKeyTreatment / interruptIsLoudest).
  • 割り込み only acts while aiState is THINKING_SPEAKING, matching the existing useInterrupt guard. It is inert in 待機 rather than sending a proactive trigger the way the old footer did.
  • Live2D now lives inside the stage, so it appears in both mode branches of App.tsx and remounts on a window↔pet flip. Upstream kept one absolutely positioned container across both modes.
  • A 30px top inset is passed to the chrome under Tauri because the desktop shell still draws its titlebar. 1b replaces that window with a transparent undecorated one and the inset returns to 0; marked with a ponytail: comment.
  • The node_modules junction to the main checkout was created; npm install was not run.

Merge overlap — read before merging

The QUESTIONS.md entry immediately above mine, from a sibling agent, describes chrome-plan.ts and use-viewport.ts components that are "unreferenced until someone mounts them" — the same App.tsx mount point this branch claims. Expect a conflict in App.tsx and possibly in components/receiver/. I did not coordinate, per the brief. Seven entries were appended to QUESTIONS.md.

Review findings

Two were raised.

Scanline not toggleable (major) — fixed. The design calls the sweep toggleable but it only responded to prefers-reduced-motion. Added a scanline?: boolean prop (default true) to ReceiverChromeProps, gated the sweep on scanline && !reducedMotion, and gave useReceiverChrome ownership of the scanline state plus an onScanlineToggle callback (both returned; App.tsx already spreads {...chrome}). The toggle UI row is deferred with a ponytail: comment naming the 設定 panel (§3a) as its home. Two tests added, confirmed red first.

Missing @keyframes (raised as blocker) — SKIPPED, the finding is factually wrong. All five keyframes exist in src/renderer/src/theme/denpa.css: carrier (36), sweep (48), blink (57), inbound (63), halate (74). src/renderer/src/main.tsx:3 imports the file. Both facts are already under test in src/renderer/src/theme/denpa-css.test.ts — line 62 asserts all five blocks exist, line 68 asserts main.tsx loads the file. The reviewer's own file list covers only system.ts and denpa-tokens.ts; they missed the CSS file #10 landed. The existing implementation is also better than the suggested fix: the keyframes are parameterised by CSS custom properties (--bar-scale, --carrier-amp, --sweep-travel) so one sweep rule serves both the 3px meter bar and the 70px stage scanline, and the reduced-motion amplitude reduction needs no second keyframe. Adding the flat hardcoded versions to system.ts would have created a duplicate, less capable definition. Since that finding's file list is mostly #10 files, it is likely wrong against the sibling branches too.

One process gap: the global CLAUDE.md asks for the cavecrew reviewer on non-trivial diffs, and as a subagent I have no agent-spawn tool, so the diff got a careful self-review instead of a second pair of eyes beyond the findings above.

Branch: `issue/denpa-12-mobile-chrome` (pushed, not merged, no PR). ## What landed Window mode in `App.tsx` now renders a `ReceiverChrome` column with Live2D as its stage. The old shell is gone from that path: the 440px sidebar, `Footer`, `Background`, `WebSocketStatus` and `Subtitle` are no longer mounted, and the corresponding `layout.tsx` entries (sidebar band, `appContainer`, `mainContent`, fixed-height footer) were removed — only the titlebar styles remain. New under `src/renderer/src/components/receiver/`: `receiver-chrome.tsx` (station bar, stage with glow + scanline + transmission band + log overlay, dock), `station-bar.tsx`, `transmission-band.tsx`, `dock.tsx`, `log-overlay.tsx`, `chrome-spec.ts` (§1a labels and geometry, plus `handsFreeReadout`, `bandTimestamp`, `pttKeyLabel`/`pttKeyTreatment`/`interruptIsLoudest` derived from #10's faceplate table) and `transmission.ts` (pure PTT/stream/interrupt state steps). Wiring lives in `hooks/receiver/use-receiver-chrome.ts` against the AiState/Subtitle/VAD contexts plus `useInterrupt` and `useMicToggle`; `AiStateContext` remains the sole owner of `aiState`. `theme/denpa-tokens.ts` gained `denpaWashes` for the chrome's alpha scrims and gradient stops, and a `transmission` text-shadow. ## Tests `npm test`: 89 passed in 11 files, 0 skipped (87 on the first pass, +2 from the review fix). Real tail: ``` Test Files 11 passed (11) Tests 89 passed (89) Duration 4.08s ``` `transmission.test.ts` (10) covers the PTT and interrupt transitions as pure steps. `receiver-chrome.test.tsx` (13, +1 from review) covers render output. `chrome-is-mounted.test.ts` (4, +1) asserts the app actually mounts the chrome and that no fixed sidebar survives. `npm run typecheck` reports the same 585 errors as the documented `main` baseline; grepping for `components/receiver`, `hooks/receiver`, `App.tsx`, `layout.tsx` and `denpa-tokens.ts` returns zero. `npm run lint` is still dead on `main` for the pre-existing reason and was not run. ## Visual claims that were never observed rendering **Nothing was ever rendered in a browser.** This is layout work reviewed only through emitted CSS. Every visual claim — that the bands read as design card 1a, that the stage glow and cyan scanline sweep sit correctly behind the model, that the dock key proportions land — is transcribed from `docs/design/denpa-receiver/README.md` §1a, not observed. The tests assert that specific CSS rules and text are emitted (`overflow`, safe-area padding, the 82px reservation, 44px minimum targets), which is evidence the rules exist, not that the result looks right. Someone should look at this in a browser before trusting the fidelity. ## Deliberately left out - **Real press-to-talk audio capture.** The key drives the mic through `VADContext` (`startMic` on pointerdown, `stopMic` on pointerup) and runs the state cycle the issue specifies, but the only path that actually sends audio is VAD's own `onSpeechEnd`, and `stopMic()` destroys the VAD instance without flushing (`src/renderer/src/context/vad-context.tsx:322`). A release before VAD's speech-end sends nothing. Fixing it needs a recorder in the mic layer, not in the chrome. - **Any composer or 調整 entry point.** 1a's dock has neither and the settings sheet is 3b/5e, so dropping the footer and sidebar leaves text input and settings unreachable on this surface. - A THINKING/SPEAKING split — `AiStateEnum` has one `THINKING_SPEAKING` value and #10 maps it to one treatment. - The decorative 88.6 frequency readout — dropped rather than faked. - 4a's 受信記録 row design — the overlay renders the upstream `ChatHistoryPanel`, so real messages with the wrong styling. - The unprompted 自発送信 break-in band, deferred per the design's own build status. ## Assumptions - The Japanese receiver labels live as data in `chrome-spec.ts` rather than as i18next keys, following #10's `faceplate-spec.ts` precedent, even though the issue says all copy goes through i18next. The product's Japanese vocabulary is not translatable copy, and splitting it across two mechanisms seemed worse than one consistent one. - The PTT key's word and skin derive from #10's faceplate table with one exception: in 送信中 the table gives the loudest control to 割り込み, which the dock already has its own key for, so the PTT keeps the press prompt instead of becoming a second 割り込み. The derivation is in one place (`pttKeyLabel` / `pttKeyTreatment` / `interruptIsLoudest`). - 割り込み only acts while `aiState` is `THINKING_SPEAKING`, matching the existing `useInterrupt` guard. It is inert in 待機 rather than sending a proactive trigger the way the old footer did. - Live2D now lives inside the stage, so it appears in both mode branches of `App.tsx` and remounts on a window↔pet flip. Upstream kept one absolutely positioned container across both modes. - A 30px top inset is passed to the chrome under Tauri because the desktop shell still draws its titlebar. 1b replaces that window with a transparent undecorated one and the inset returns to 0; marked with a `ponytail:` comment. - The `node_modules` junction to the main checkout was created; `npm install` was not run. ## Merge overlap — read before merging The `QUESTIONS.md` entry immediately above mine, from a sibling agent, describes `chrome-plan.ts` and `use-viewport.ts` components that are "unreferenced until someone mounts them" — the same `App.tsx` mount point this branch claims. **Expect a conflict in `App.tsx` and possibly in `components/receiver/`.** I did not coordinate, per the brief. Seven entries were appended to `QUESTIONS.md`. ## Review findings Two were raised. **Scanline not toggleable (major) — fixed.** The design calls the sweep toggleable but it only responded to `prefers-reduced-motion`. Added a `scanline?: boolean` prop (default true) to `ReceiverChromeProps`, gated the sweep on `scanline && !reducedMotion`, and gave `useReceiverChrome` ownership of the `scanline` state plus an `onScanlineToggle` callback (both returned; `App.tsx` already spreads `{...chrome}`). The toggle UI row is deferred with a `ponytail:` comment naming the 設定 panel (§3a) as its home. Two tests added, confirmed red first. **Missing `@keyframes` (raised as blocker) — SKIPPED, the finding is factually wrong.** All five keyframes exist in `src/renderer/src/theme/denpa.css`: `carrier` (36), `sweep` (48), `blink` (57), `inbound` (63), `halate` (74). `src/renderer/src/main.tsx:3` imports the file. Both facts are already under test in `src/renderer/src/theme/denpa-css.test.ts` — line 62 asserts all five blocks exist, line 68 asserts `main.tsx` loads the file. The reviewer's own file list covers only `system.ts` and `denpa-tokens.ts`; they missed the CSS file #10 landed. The existing implementation is also better than the suggested fix: the keyframes are parameterised by CSS custom properties (`--bar-scale`, `--carrier-amp`, `--sweep-travel`) so one `sweep` rule serves both the 3px meter bar and the 70px stage scanline, and the reduced-motion amplitude reduction needs no second keyframe. Adding the flat hardcoded versions to `system.ts` would have created a duplicate, less capable definition. Since that finding's file list is mostly #10 files, it is likely wrong against the sibling branches too. One process gap: the global `CLAUDE.md` asks for the cavecrew reviewer on non-trivial diffs, and as a subagent I have no agent-spawn tool, so the diff got a careful self-review instead of a second pair of eyes beyond the findings above.
Author
Owner

Shipped. Merged to main at d73f4ed (phase B2i — client chrome integration).

Closed as part of reconciling the board with main: the autonomous run's policy was not to auto-close, so shipped work stayed open and the tracker read as though nothing had landed. That policy is now reversed — issues close when they land.

Shipped. Merged to `main` at [`d73f4ed`](https://git.aiko.works/aiko/denpa/commit/d73f4ed) (phase B2i — client chrome integration). Closed as part of reconciling the board with `main`: the autonomous run's policy was not to auto-close, so shipped work stayed open and the tracker read as though nothing had landed. That policy is now reversed — issues close when they land.
aiko closed this issue 2026-07-30 13:32:53 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#12
No description provided.