Skip to content

feat(scene): mirror the API-key Setup form into the scene frame#998

Merged
esengine merged 1 commit into
mainfrom
rust-scene-setup-mirror
May 16, 2026
Merged

feat(scene): mirror the API-key Setup form into the scene frame#998
esengine merged 1 commit into
mainfrom
rust-scene-setup-mirror

Conversation

@esengine
Copy link
Copy Markdown
Owner

First step toward un-blocking the fresh-user path under
`REASONIX_RENDERER=rust`. Direction-B follow-on to #997.

Why

The Ink-side `Setup` screen rendered to the null stdout, so a user
with no saved API key saw nothing at all and had no way to know the
loop was waiting on their key. The scene producer now emits a
parallel setup frame so the prompt is visible:

```
✦ reasonix · welcome
Enter your DeepSeek API key:
get one at https://platform.deepseek.com
❯ •••••▮
✗ (only when error is set)
Ctrl+C to exit · /exit to quit
```

What

  • New `buildSetupFrame(input, cols, rows)` — completely separate
    from `buildTraceFrame`. The setup screen never coexists with
    cards / status / composer, so a shared layout would only constrain
    both.
  • New `useSetupSceneTrace` hook — `Setup.tsx` calls it with
    `bufferLength` and the current `error` string.
  • Only the buffer length crosses the scene boundary. Raw key
    material never gets serialized into the scene stream — the scene
    layer never sees the API key, just the count of dots to render.

Known limitation (next PR)

No keystroke change in this PR — `MaskedInput` keeps using Ink's
`useInput`. Under `REASONIX_RENDERER=rust` Ink is wired to the
null stdin (see `src/cli/ui/scene/null-stdin.ts`), so `useInput`
receives nothing and the user still can't actually type a key
without falling back to `REASONIX_RENDERER`-unset mode. Closing
that gap means wiring `KeystrokeProvider` around the pre-App
`` mount and routing the rust input child's keys to
`MaskedInput`. That's a separate, scoped follow-up.

This PR is intentionally just the visible-feedback half — the user
at least learns "the loop wants my API key" instead of staring at
a blank terminal.

Tests

`tests/scene-trace-frame.test.ts` — 5 new cases:

  • welcome / prompt / placeholder / exit hint when buffer is empty
  • `•` dots + `▮` cursor when the user has typed
  • buffer-length-only crossing the boundary (5 chars → exactly 5 `•`)
  • error row inserted above the exit hint when an error is set
  • error row omitted when error is undefined

`npm run verify` green via prepush gate.

Refs #868

First step toward un-blocking the fresh-user path under
REASONIX_RENDERER=rust. The Setup screen rendered to the null Ink
stdout and was completely invisible; a user with no saved key saw
nothing at all and had no way to know the loop was waiting on input.
The scene producer now emits a parallel setup frame:

  ✦ reasonix · welcome
  Enter your DeepSeek API key:
    get one at https://platform.deepseek.com
  ❯ •••••▮
  ✗ <error>            (only when error is set)
  Ctrl+C to exit · /exit to quit

- New buildSetupFrame(input, cols, rows) — completely separate from
  buildTraceFrame; the setup screen never coexists with cards / status
  / composer so a shared layout would only constrain both.
- New useSetupSceneTrace hook — Setup.tsx calls it with bufferLength
  and the current error string. Only the buffer LENGTH crosses the
  scene boundary; raw key material never gets serialized into the
  scene stream.
- No keystroke change in this PR — MaskedInput keeps using Ink's
  useInput. That gap (stdin is the null stream under rust mode, so
  useInput receives nothing) becomes a real interactive Setup in a
  follow-up.

Refs #868
@esengine esengine merged commit 292c8aa into main May 16, 2026
5 checks passed
@esengine esengine deleted the rust-scene-setup-mirror branch May 16, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant