電波 design tokens, carrier meter, and the state faceplate #10

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

What to build

The foundation every other slice draws on, delivered end-to-end by replacing the current purple idle pill with the real state indicator.

Tokens — express the colour, type, spacing, radius and shadow scales from the handoff as Chakra v3 theme/semantic tokens. Do not port inline styles from the design HTML; they are inline only because the design tool required it.

Fonts — Archivo (400/500/600/800/900) and DotGothic16 (400), self-hosted so the Tauri build works offline. DotGothic16 is a bitmap gothic; a generic mono is not a substitute.

Animationscarrier, sweep, blink, inbound, halate as named keyframes. Under prefers-reduced-motion, drop sweep and halate but keep carrier at reduced amplitude — it is data, not decoration.

Carrier meter + state faceplate — one shared component driven by AiStateContext, covering every AiStateEnum value per the 1d table (IDLE floor, LISTENING bound to real mic level, THINKING_SPEAKING halate band, INTERRUPTED rules, LOADING tuning static, WAITING dimmed floor). Mount it where the state pill is today.

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

Acceptance criteria

  • Tokens available as Chakra semantic tokens; no hardcoded hexes in new components
  • Both fonts self-hosted and rendering offline in the Tauri build
  • Every AiStateEnum value renders its faceplate treatment; LISTENING bar heights track real mic level
  • prefers-reduced-motion drops sweep/halate, keeps carrier at reduced amplitude
  • Old state pill removed
  • Tests cover the state → treatment mapping

Blocked by

None - can start immediately

## What to build The foundation every other slice draws on, delivered end-to-end by replacing the current purple `idle` pill with the real state indicator. **Tokens** — express the colour, type, spacing, radius and shadow scales from the handoff as Chakra v3 theme/semantic tokens. Do not port inline styles from the design HTML; they are inline only because the design tool required it. **Fonts** — Archivo (400/500/600/800/900) and DotGothic16 (400), **self-hosted** so the Tauri build works offline. DotGothic16 is a bitmap gothic; a generic mono is not a substitute. **Animations** — `carrier`, `sweep`, `blink`, `inbound`, `halate` as named keyframes. Under `prefers-reduced-motion`, drop `sweep` and `halate` but keep `carrier` at reduced amplitude — it is data, not decoration. **Carrier meter + state faceplate** — one shared component driven by `AiStateContext`, covering every `AiStateEnum` value per the 1d table (IDLE floor, LISTENING bound to real mic level, THINKING_SPEAKING halate band, INTERRUPTED rules, LOADING tuning static, WAITING dimmed floor). Mount it where the state pill is today. Design reference: `docs/design/denpa-receiver/README.md` (card ids `1a`, `4a`… index into `Denpa Receiver.dc.html`, openable in a browser). — 1d is the contract table. ## Acceptance criteria - [ ] Tokens available as Chakra semantic tokens; no hardcoded hexes in new components - [ ] Both fonts self-hosted and rendering offline in the Tauri build - [ ] Every `AiStateEnum` value renders its faceplate treatment; LISTENING bar heights track real mic level - [ ] `prefers-reduced-motion` drops sweep/halate, keeps carrier at reduced amplitude - [ ] Old state pill removed - [ ] Tests cover the state → treatment mapping ## Blocked by None - can start immediately
Author
Owner

Landed on issue/denpa-10-design-tokens.

Shapesrc/renderer/src/theme/ holds denpa-tokens.ts (the raw scales, the only place hexes live), system.ts (denpaSystem, mounted by both ChakraProvider call sites) and denpa.css (self-hosted @font-face plus the five named keyframes). src/renderer/src/components/receiver/ holds faceplate-spec.ts (the 1d table as data: JP + EN label, meter treatment, key treatment, partial-transmission rule, plus motionPlan and truncatePartial), carrier-bars.ts (bar geometry — count, step, colour, level), carrier-meter.tsx and state-faceplate.tsx. The footer's purple #7C5CFF pill and ai-state-indicator.tsx are gone.

Tests — 28 new, 56 total (npm test: 8 files, 56 passed; baseline was 28). They cover: every AiStateEnum value has a treatment and an unknown state throws rather than defaulting to a working IDLE; the six JP/EN labels exactly, with a character-set guard so nobody reuses the zh locale; WAITING holds the IDLE meter floor and differs only in the key; LOADING stays distinct from the idle floor and from listening-at-silence on a non-colour property; LISTENING heights monotonic in mic level with every bar moving, and clamped against the 0-100 vs 0-1 unit mismatch that exists in vad-context.tsx; listening-at-level-0 is not the idle floor; station/pal/faceplate geometry derives from one step; reduced motion drops sweep and halate and keeps carrier at reduced amplitude; the faceplate renders exactly one role="status" region carrying both labels with the bars aria-hidden, and mic-level changes do not re-announce; the Chakra system actually resolves every role token; no remote font URL anywhere, every url() resolving to a committed file; no raw hex under components/receiver/.

npm run build:web was run as a check that the tokens compile and the fonts ship: the woff2 files land in dist/web/fonts/ with the url()s rewritten relative, so the packaged build resolves them offline.

Left out — 自発 / UNPROMPTED is in the 1d table but is not an AiStateEnum value and its band is deferred, so it is absent rather than stubbed. No global body-font override and no dock/station-bar chrome; this issue only remounts the state indicator where the pill was.

Assumed (all six written to the questions ledger) — the JP labels are spec strings, not i18n copy; the mic level is VADContext.previousTriggeredProbability, a peak-hold that never falls mid-utterance, since it is the only level the client has; the px spacing scale is deliberately not registered as Chakra tokens because it would shadow the rem steps this fork already uses; fonts are subset woff2 committed to public/fonts/; faceplateFor throws on an unknown state.

Finding, not from this branchnpm run lint and npm run typecheck are already red on main. Lint dies before linting (eslint-config-airbnb referenced by .eslintrc.js, not installed). Typecheck reports 585 errors on main and 585 on this branch, almost all in the vendored WebSDK/ and MotionSync/ trees; this diff adds zero.

Landed on `issue/denpa-10-design-tokens`. **Shape** — `src/renderer/src/theme/` holds `denpa-tokens.ts` (the raw scales, the only place hexes live), `system.ts` (`denpaSystem`, mounted by both `ChakraProvider` call sites) and `denpa.css` (self-hosted `@font-face` plus the five named keyframes). `src/renderer/src/components/receiver/` holds `faceplate-spec.ts` (the 1d table as data: JP + EN label, meter treatment, key treatment, partial-transmission rule, plus `motionPlan` and `truncatePartial`), `carrier-bars.ts` (bar geometry — count, step, colour, level), `carrier-meter.tsx` and `state-faceplate.tsx`. The footer's purple `#7C5CFF` pill and `ai-state-indicator.tsx` are gone. **Tests** — 28 new, 56 total (`npm test`: 8 files, 56 passed; baseline was 28). They cover: every `AiStateEnum` value has a treatment and an unknown state throws rather than defaulting to a working IDLE; the six JP/EN labels exactly, with a character-set guard so nobody reuses the `zh` locale; WAITING holds the IDLE meter floor and differs only in the key; LOADING stays distinct from the idle floor and from listening-at-silence on a non-colour property; LISTENING heights monotonic in mic level with every bar moving, and clamped against the 0-100 vs 0-1 unit mismatch that exists in `vad-context.tsx`; listening-at-level-0 is not the idle floor; station/pal/faceplate geometry derives from one step; reduced motion drops sweep and halate and keeps carrier at reduced amplitude; the faceplate renders exactly one `role="status"` region carrying both labels with the bars `aria-hidden`, and mic-level changes do not re-announce; the Chakra system actually resolves every role token; no remote font URL anywhere, every `url()` resolving to a committed file; no raw hex under `components/receiver/`. `npm run build:web` was run as a check that the tokens compile and the fonts ship: the woff2 files land in `dist/web/fonts/` with the `url()`s rewritten relative, so the packaged build resolves them offline. **Left out** — 自発 / UNPROMPTED is in the 1d table but is not an `AiStateEnum` value and its band is deferred, so it is absent rather than stubbed. No global body-font override and no dock/station-bar chrome; this issue only remounts the state indicator where the pill was. **Assumed** (all six written to the questions ledger) — the JP labels are spec strings, not i18n copy; the mic level is `VADContext.previousTriggeredProbability`, a peak-hold that never falls mid-utterance, since it is the only level the client has; the px spacing scale is deliberately *not* registered as Chakra tokens because it would shadow the rem steps this fork already uses; fonts are subset woff2 committed to `public/fonts/`; `faceplateFor` throws on an unknown state. **Finding, not from this branch** — `npm run lint` and `npm run typecheck` are already red on `main`. Lint dies before linting (`eslint-config-airbnb` referenced by `.eslintrc.js`, not installed). Typecheck reports 585 errors on `main` and 585 on this branch, almost all in the vendored `WebSDK/` and `MotionSync/` trees; this diff adds zero.
Author
Owner

Review round on issue/denpa-10-design-tokens — pushed. This supersedes the test counts in my comment above; everything else there still holds.

What was builtsrc/renderer/src/theme/ holds denpa-tokens.ts (raw scales; the only place hexes live), system.ts (denpaSystem, mounted by both ChakraProvider call sites) and denpa.css (self-hosted @font-face plus the five named keyframes). src/renderer/src/components/receiver/ holds faceplate-spec.ts (the 1d table as data: JP + EN label, meter treatment, key treatment, partial-transmission rule, plus motionPlan and truncatePartial), carrier-bars.ts (bar geometry), carrier-meter.tsx and state-faceplate.tsx. The footer's purple #7C5CFF pill and ai-state-indicator.tsx are deleted.

What the review caught, and what changed

  • Blocker — INTERRUPTED drew four carrier bars instead of 1d's two rules. state-faceplate.tsx now has a SeveredRules meter: two 2px unprompted-token rules with 切断 between, inside the 52x26 band footprint, never reaching CarrierMeter.
  • Blocker — LOADING drew carrier bars instead of 1d's tuning static. Added a TuningStatic meter painting repeating-linear-gradient(115deg, rgba(255,45,149,.28) 0 3px, transparent 3px 9px) with a 10px rgba(53,240,232,.5) bar on the sweep keyframe at 1600ms linear.
  • Major — THINKING_SPEAKING's halate band used the solid carrier colour. HalateBand now backgrounds at rgba(255,45,149,.14) with linear-gradient(90deg, transparent, rgba(255,45,149,.75), transparent) on an absolutely-positioned child carrying the animation. Rendered markup verified to contain no #ff2d95.
  • Major — tests only exercised LISTENING's rendering. Three rendering tests added (severed rules + 切断; tuning gradient + animation-name: sweep; halate band + gradient overlay), each also asserting the carrier bars are absent. All three were confirmed failing against the pre-fix component for the right reason before the fix. Plus a faceplate-spec test pinning both gradient strings to the 1d recipes.

Two supporting changes fell out of that. A denpaColors.meter group (band/halate/static/sweep) in theme/denpa-tokens.ts keeps the three translucent fills where raw values live — the no-raw-hex test still passes. And the shared sweep keyframe now reads its end position from --sweep-travel (default 900%); its fixed -140% → 900% travel was authored for a thin bar in a tall meter and moved a 10px bar 104px through a 26px band, visible only about 35% of the cycle. The tuning band sets --sweep-travel: 26px. No other consumer of that keyframe exists.

One thing worth knowing about the test style: the rendering tests use a meterMarkup() slice helper rather than asserting on the whole document. Chakra emits every registered token as a CSS var in an inline <style> block, so a document-wide toContain on a colour or gradient string passes on the token declaration alone — a false green, and it is the assertion style the review suggested. The assertions are scoped to the meter element via data-denpa-meter markers.

Testsnpm test: 8 files, 60 passed, 2.64s. Baseline was 28. Nothing from the findings list was skipped.

 RUN  v4.1.10 F:/Projects/denpa-repos/denpa-client
 Test Files  8 passed (8)
      Tests  60 passed (60)
   Duration  2.64s

What did not pass

Two checks are red, both red at HEAD and unrelated to this diff. npm run lint never lints: ESLint 8.57.1 aborts with couldn't find the config "airbnb" because eslint-config-airbnb is not installed. npm run typecheck reports 585 error lines — 582 in the vendored src/renderer/WebSDK/, 3 pre-existing unused-import errors in files this branch does not touch. I stashed the diff and re-ran both: counts identical with and without it, so this round adds zero type errors. Not fixed, per the scope rule; recorded in the questions ledger.

Also: the cavecrew-reviewer pass the global instructions call for could not run — this agent has no agent-spawn tool — so review was a manual diff pass. That process step is unmet.

Deliberately left out

自発 / UNPROMPTED is in the 1d table but is not an AiStateEnum value and its break-in band waits on the adapter carrying a pushed turn, so it is absent rather than stubbed. No global body-font override, no dock key row, no station/pal bar chrome; the faceplate mounts only where the pill was, and the station/pal geometry is reachable via carrierBars({variant:'station'}) for the branches that build those bands. The px spacing scale is exported as denpaSpace values but not registered as Chakra tokens — numeric keys would shadow the rem steps this fork uses everywhere. LOADING keeps carrier in its motion list even though the faceplate no longer draws bars for it, because the parked 2b tab shows a carrier for LOADING off the same spec row. Under reduced motion the tuning static holds still (sweep dropped by motionPlan, as its existing test requires) and stays distinguishable from the IDLE floor by pattern rather than movement. The design's #7c6288 (~4.0:1) was avoided for the faceplate's EN label in favour of text.mute, since that label carries state rather than being redundant with the meter.

Assumptions

All are in the questions ledger.

  • No jsdom and no @testing-library were added, contrary to the spec plan. The a11y contract is asserted against real markup via react-dom/server's renderToStaticMarkup inside a ChakraProvider, which runs in the existing environment: 'node'. Zero new dependencies; the original 28 tests and the global vitest config are untouched.
  • Also against the spec plan: Chakra resolves semantic tokens to CSS vars, so denpaSystem.token("colors.carrier") === "#ff2d95" can never pass. The wiring test instead asserts every role token is registered (tokens.getByName defined, token() truthy — undefined is exactly what wrong config nesting yields) and that the raw scale under colors.denpa.* resolves to the hex.
  • Archivo ships as one variable woff2 covering weight 100–900, not five static faces; DotGothic16 ships as latin + japanese subsets. The font test asserts the declared weight ranges cover 400/500/600/800/900 rather than counting five @font-face blocks.
  • previousTriggeredProbability is reachable from VADContext and does trigger re-renders (setPreviousTriggeredProbability calls forceUpdate, and it resets to 0 on speech end / stopMic). It is a peak-hold that never falls mid-utterance, which is the level LISTENING is bound to. carrierBars takes a plain number, so a decaying level later touches only VADContext.
  • StateFaceplate takes the mic level as a prop rather than reading VADContext: importing vad-context pulls the Live2D/Cubism chain (@framework alias) into every surface showing state and makes it unrenderable in tests. State still comes from AiStateContext via useContext, so no provider is needed in tests. The footer passes useVAD().previousTriggeredProbability.
  • prefers-reduced-motion is handled in JS (framer-motion's useReducedMotion feeding the pure motionPlan), not a CSS media block, so there is one mechanism and it is the one under test. denpa.css holds only @font-face and the five keyframes; the carrier keyframe reads --bar-scale and --carrier-amp so amplitude reduction composes with the data-driven bar height.
  • Reduced carrier amplitude is 0.45 — the design says only "reduced". The test pins strictly-between-zero-and-full, not the number.
  • LISTENING's silence floor is [.34,.30,.34,.32], above IDLE's [.2,.28,.2,.24], so mic-open-and-silent never draws as mic-closed. Level maps as floor + (1-floor) * level * weight with weights [1,.88,1,.82], so every bar moves and none exceeds 1.
  • INTERRUPTED's meter is assumed frozen — two acid rules at a flat scale, no motion, no exit rule — since 1d gives colour and 切断 but no bar behaviour and no auto-return.
  • LOADING keeps carrier under reduced motion (only sweep is dropped) so tuning-with-no-data-yet never reads as dead. Silence, not-yet-loaded and idle are pairwise distinct on kind and scale profile, not on hue.
  • faceplateFor throws on an unknown state rather than falling back. AiState is a closed union so this is unreachable in practice, but there is no error boundary above the footer if it ever is reached.
  • The JP labels live in faceplate-spec.ts as product markings rather than i18n keys, so the visible label and the screen-reader announcement cannot drift. The now-unused aiState.* keys in locales/en and locales/zh were left alone as out of scope.

No PR opened, nothing closed, nothing merged, main untouched.

Review round on `issue/denpa-10-design-tokens` — pushed. This supersedes the test counts in my comment above; everything else there still holds. **What was built** — `src/renderer/src/theme/` holds `denpa-tokens.ts` (raw scales; the only place hexes live), `system.ts` (`denpaSystem`, mounted by both `ChakraProvider` call sites) and `denpa.css` (self-hosted `@font-face` plus the five named keyframes). `src/renderer/src/components/receiver/` holds `faceplate-spec.ts` (the 1d table as data: JP + EN label, meter treatment, key treatment, partial-transmission rule, plus `motionPlan` and `truncatePartial`), `carrier-bars.ts` (bar geometry), `carrier-meter.tsx` and `state-faceplate.tsx`. The footer's purple `#7C5CFF` pill and `ai-state-indicator.tsx` are deleted. ## What the review caught, and what changed - *Blocker* — INTERRUPTED drew four carrier bars instead of 1d's two rules. `state-faceplate.tsx` now has a `SeveredRules` meter: two 2px unprompted-token rules with 切断 between, inside the 52x26 band footprint, never reaching `CarrierMeter`. - *Blocker* — LOADING drew carrier bars instead of 1d's tuning static. Added a `TuningStatic` meter painting `repeating-linear-gradient(115deg, rgba(255,45,149,.28) 0 3px, transparent 3px 9px)` with a 10px `rgba(53,240,232,.5)` bar on the `sweep` keyframe at 1600ms linear. - *Major* — THINKING_SPEAKING's halate band used the solid carrier colour. `HalateBand` now backgrounds at `rgba(255,45,149,.14)` with `linear-gradient(90deg, transparent, rgba(255,45,149,.75), transparent)` on an absolutely-positioned child carrying the animation. Rendered markup verified to contain no `#ff2d95`. - *Major* — tests only exercised LISTENING's rendering. Three rendering tests added (severed rules + 切断; tuning gradient + `animation-name: sweep`; halate band + gradient overlay), each also asserting the carrier bars are *absent*. All three were confirmed failing against the pre-fix component for the right reason before the fix. Plus a `faceplate-spec` test pinning both gradient strings to the 1d recipes. Two supporting changes fell out of that. A `denpaColors.meter` group (band/halate/static/sweep) in `theme/denpa-tokens.ts` keeps the three translucent fills where raw values live — the no-raw-hex test still passes. And the shared `sweep` keyframe now reads its end position from `--sweep-travel` (default `900%`); its fixed `-140% → 900%` travel was authored for a thin bar in a tall meter and moved a 10px bar 104px through a 26px band, visible only about 35% of the cycle. The tuning band sets `--sweep-travel: 26px`. No other consumer of that keyframe exists. One thing worth knowing about the test style: the rendering tests use a `meterMarkup()` slice helper rather than asserting on the whole document. Chakra emits every registered token as a CSS var in an inline `<style>` block, so a document-wide `toContain` on a colour or gradient string passes on the token *declaration* alone — a false green, and it is the assertion style the review suggested. The assertions are scoped to the meter element via `data-denpa-meter` markers. **Tests** — `npm test`: 8 files, 60 passed, 2.64s. Baseline was 28. Nothing from the findings list was skipped. ``` RUN v4.1.10 F:/Projects/denpa-repos/denpa-client Test Files 8 passed (8) Tests 60 passed (60) Duration 2.64s ``` ## What did not pass Two checks are red, both red at HEAD and unrelated to this diff. `npm run lint` never lints: ESLint 8.57.1 aborts with `couldn't find the config "airbnb"` because `eslint-config-airbnb` is not installed. `npm run typecheck` reports 585 error lines — 582 in the vendored `src/renderer/WebSDK/`, 3 pre-existing unused-import errors in files this branch does not touch. I stashed the diff and re-ran both: counts identical with and without it, so this round adds zero type errors. Not fixed, per the scope rule; recorded in the questions ledger. Also: the `cavecrew-reviewer` pass the global instructions call for could not run — this agent has no agent-spawn tool — so review was a manual diff pass. That process step is unmet. ## Deliberately left out 自発 / UNPROMPTED is in the 1d table but is not an `AiStateEnum` value and its break-in band waits on the adapter carrying a pushed turn, so it is absent rather than stubbed. No global body-font override, no dock key row, no station/pal bar chrome; the faceplate mounts only where the pill was, and the station/pal geometry is reachable via `carrierBars({variant:'station'})` for the branches that build those bands. The px spacing scale is exported as `denpaSpace` values but not registered as Chakra tokens — numeric keys would shadow the rem steps this fork uses everywhere. LOADING keeps `carrier` in its motion list even though the faceplate no longer draws bars for it, because the parked 2b tab shows a carrier for LOADING off the same spec row. Under reduced motion the tuning static holds still (sweep dropped by `motionPlan`, as its existing test requires) and stays distinguishable from the IDLE floor by pattern rather than movement. The design's `#7c6288` (~4.0:1) was avoided for the faceplate's EN label in favour of `text.mute`, since that label carries state rather than being redundant with the meter. ## Assumptions All are in the questions ledger. - No jsdom and no `@testing-library` were added, contrary to the spec plan. The a11y contract is asserted against real markup via `react-dom/server`'s `renderToStaticMarkup` inside a `ChakraProvider`, which runs in the existing `environment: 'node'`. Zero new dependencies; the original 28 tests and the global vitest config are untouched. - Also against the spec plan: Chakra resolves semantic tokens to CSS vars, so `denpaSystem.token("colors.carrier") === "#ff2d95"` can never pass. The wiring test instead asserts every role token is registered (`tokens.getByName` defined, `token()` truthy — `undefined` is exactly what wrong config nesting yields) and that the raw scale under `colors.denpa.*` resolves to the hex. - Archivo ships as one variable woff2 covering weight 100–900, not five static faces; DotGothic16 ships as latin + japanese subsets. The font test asserts the declared weight ranges cover 400/500/600/800/900 rather than counting five `@font-face` blocks. - `previousTriggeredProbability` *is* reachable from `VADContext` and does trigger re-renders (`setPreviousTriggeredProbability` calls `forceUpdate`, and it resets to 0 on speech end / `stopMic`). It is a peak-hold that never falls mid-utterance, which is the level LISTENING is bound to. `carrierBars` takes a plain number, so a decaying level later touches only `VADContext`. - `StateFaceplate` takes the mic level as a prop rather than reading `VADContext`: importing `vad-context` pulls the Live2D/Cubism chain (`@framework` alias) into every surface showing state and makes it unrenderable in tests. State still comes from `AiStateContext` via `useContext`, so no provider is needed in tests. The footer passes `useVAD().previousTriggeredProbability`. - `prefers-reduced-motion` is handled in JS (framer-motion's `useReducedMotion` feeding the pure `motionPlan`), not a CSS media block, so there is one mechanism and it is the one under test. `denpa.css` holds only `@font-face` and the five keyframes; the carrier keyframe reads `--bar-scale` and `--carrier-amp` so amplitude reduction composes with the data-driven bar height. - Reduced carrier amplitude is `0.45` — the design says only "reduced". The test pins strictly-between-zero-and-full, not the number. - LISTENING's silence floor is `[.34,.30,.34,.32]`, above IDLE's `[.2,.28,.2,.24]`, so mic-open-and-silent never draws as mic-closed. Level maps as `floor + (1-floor) * level * weight` with weights `[1,.88,1,.82]`, so every bar moves and none exceeds 1. - INTERRUPTED's meter is assumed frozen — two acid rules at a flat scale, no motion, no exit rule — since 1d gives colour and 切断 but no bar behaviour and no auto-return. - LOADING keeps `carrier` under reduced motion (only `sweep` is dropped) so tuning-with-no-data-yet never reads as dead. Silence, not-yet-loaded and idle are pairwise distinct on kind and scale profile, not on hue. - `faceplateFor` throws on an unknown state rather than falling back. `AiState` is a closed union so this is unreachable in practice, but there is no error boundary above the footer if it ever is reached. - The JP labels live in `faceplate-spec.ts` as product markings rather than i18n keys, so the visible label and the screen-reader announcement cannot drift. The now-unused `aiState.*` keys in `locales/en` and `locales/zh` were left alone as out of scope. No PR opened, nothing closed, nothing merged, `main` untouched.
Author
Owner

Shipped. Merged to main at 875a261 (phase B1 — client foundation).

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 [`875a261`](https://git.aiko.works/aiko/denpa/commit/875a261) (phase B1 — client foundation). 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:52 +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#10
No description provided.