Skip to content

chat: onboarding answers the pointer and a second enter, no telemetry notice or command, and free models only on a low account - #1720

Merged
AbirAbbas merged 27 commits into
devfrom
zeropoint95/onboarding-ux-cleanups
Oct 2, 2026
Merged

AbirAbbas merged 27 commits into
devfrom
zeropoint95/onboarding-ux-cleanups

Conversation

@ZeroPoint95

@ZeroPoint95 ZeroPoint95 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

First-run fixes, plus the owner's follow-ups on the same screen. Every item below is in docs/changes/unreleased/1720-onboarding-ux-cleanups.md as an invalidates line; the manual's getting-started, openrouter-credits and running-from-the-terminal pages follow it.

New UX looks like this

1720

1. The interactive menus

  • The welcome screen's starting points took every enter. The first enter filled the box; the second, the one that sends, was still taken by the list and sent nothing. With words in the box, enter is the composer's: ↓ enter enter sends.
  • The setup's controls screen swallowed every mouse press. Rows now answer a press the way the key would (limit focuses, chat model opens the list, a model in the list is taken, Start a conversation leaves); the wheel scrolls the open list.
  • Enter goes on. After a model is taken the focus moves to the next row, so enter alone walks the whole form.

2. Telemetry

  • The usage-counts notice no longer appears on the first conversation's screen, ahead of --once, or ahead of a task; the "nothing sent until shown" gate goes with it.
  • codeaf telemetry (status, info, show, on, off) is removed. The disclosure is the README's Telemetry section and docs/TELEMETRY.md, held to the switches by the telemetry package's doc test. The switches are unchanged: /settings telemetry toggle, CODEAF_TELEMETRY=off, DO_NOT_TRACK=1, the project file, an empty endpoint.

3. OpenRouter account state

  • Low balance: the setup's model list is cut to free rows (:free ids and zero-priced catalog rows) and says free only; the model in use stays on the list. The warning stands right-aligned on the frame's last row, where the keys row carries it outside the setup.
  • Expired key (new): a balance read answered API key expired is a reading of its own (credits.Reading.Expired, kept beside low in credits.json). Your OpenRouter key has expired — make a new one at openrouter.ai/settings/keys stands under the message box in a conversation and on Home for every OpenRouter model, free included; the setup says it on its last row; a turn refused as expired asks for a fresh read through provider.APIError.KeyExpired (the taxonomy law forbids reading the status in the surface); the free defaults are not used for it.

4. The controls screen itself (owner's follow-ups)

  • One column: the example panel on top (up to 92 cells, whole or not at all), two blank rows, the heading Basic settings, the keys line directly under it, then the rows. The old two-column layout, Models and spending + lead sentence, and the panel's An illustration… foot line are gone.
  • The panel's edge carries the example's title; a fifth example, Hand off complex coding tasks, asks /senior-dev …; commands in requests, explanations and the note wear the composer's chip.
  • The panel turns on its own clock: opens on 1/5, next every 3 s round the ring; ←/→ browse; any key holds the clock 3 s; the focus never moves it; the screen-reader tier never turns by itself.
  • Row names: body ink until answered, accent while focused, dim once enter has acted. Keys line: enter sets the limit · ↑↓ moves · …, no tab moves, no ←→ examples. The loose enter beside Start a conversation is gone.
  • The review row (Other settings use defaults · review) is removed; a dim note Everything else is in /settings stands under the way out.
  • The model list is a flat list of exact ids. The limit's explanation names /budget, the model's /model; ? on the limit explains the day's and the conversation's ceilings and how they meet.

Proof

  • Tests cover every item above (internal/tui3, internal/credits, internal/config, internal/provider, internal/telemetry, cmd/codeaf), the manual gate has new probes, the untagged internal/e2e needle gate and make test-laws are green, and make test-quick is green on the branch head.
  • Verified by hand in tmux against a fresh home: clicks on every row, the welcome without the notice, ↓ enter enter sending, the one-column layout at 130×40 and its fallback at 120×24, the panel turning on its clock and holding on keys, the chips in the capture's escape bytes, and the expired-key warning on the welcome screen against a local stub.
  • Not seen by hand: the low-credits and expired-key lines on the setup's last row (the paste-a-key road needs a reachable OpenRouter); their placement is pinned by unit tests.
  • make pr-ready has not been run on this branch.

Open question left to the owner: the ←/→ glyphs read as asymmetric in the terminal font; every mirror-image alternative is a vocabulary glyph with another meaning, so they were left as they are.

A Linux arm64 build of the head is packaged as a Docker image for first-run trials (docker run -it --rm codeaf-onboarding); the Dockerfile lives outside the repository.

🤖 Generated with Claude Code

ZeroPoint95 added a commit that referenced this pull request Oct 1, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ZeroPoint95
ZeroPoint95 marked this pull request as draft October 1, 2026 15:22
ZeroPoint95 added a commit that referenced this pull request Oct 1, 2026
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ZeroPoint95
ZeroPoint95 force-pushed the zeropoint95/onboarding-ux-cleanups branch from 6bff8fc to 8576e80 Compare October 1, 2026 20:10
ZeroPoint95 and others added 16 commits October 1, 2026 16:52
… notice or command, and free models only on a low account

Santosh's first-run notes (2026-09-30), the non-Teams half.

- The welcome screen's starting points took every enter: the first filled
  the box, the second did nothing — four of five new people read that as
  enter not working. With words in the box, enter is the composer's.
- The Models and spending screen swallowed every press; its rows now act
  like the key they stand for (a press on the model row opens the list, on
  a model takes it, on Start leaves), the wheel scrolls the open list, and
  enter goes on after a model is taken or a review is shown, so enter
  alone walks the whole form.
- The six-line usage-counts notice is gone from the first conversation's
  screen, from --once and from task commands, and the send gate on it with
  it; `codeaf telemetry` (status, info, show, on, off) is removed. The
  disclosure is README.md and docs/TELEMETRY.md; the switches are the
  settings sheet's telemetry row, CODEAF_TELEMETRY, DO_NOT_TRACK and the
  project file, unchanged.
- While the OpenRouter account reads low, the setup's chat-model list is
  cut to free rows, says `free only`, and a line under the field says why.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…message box and on the setup screen

A balance read that OpenRouter answered with `API key expired` was a failed
read: nothing recorded, nothing said, and the next turn failed with a plain
auth error. The 401 that says expired is now a reading of its own
(credits.Reading.Expired, kept beside low in credits.json): `Your OpenRouter
key has expired — make a new one at openrouter.ai/settings/keys` stands at
the right of the keys row in a conversation and on Home for every model the
default service serves, free included, and under the chat model on the
setup screen, where the list is not cut because the free rows fail on the
same key. A turn refused as expired asks for a fresh read, through a fact
stamped on the provider's refusal (APIError.KeyExpired) so no surface reads
the status for itself. A new key clears it before it is read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… window allows

The example was a second column beside the form, 36 cells wide, drawn
from 112 columns up and level with the first field: on a tall window the
lower half of the screen stayed empty while the panel wrapped every
sentence three ways. It is one column now — the form, its keys line, one
blank row, then the panel at up to 92 cells, whole or not at all. A window
with no rows to spare under the form draws the form alone and the keys
line stops naming the arrows. The form is 64 cells on every window.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ts title on its edge, and shows a /senior-dev hand-off

Under the form, the legend stood between the form and the panel and read
as a caption for the wrong one. The order is now the panel, two blank
rows, the keys line, and the form directly under it. The panel's top edge
reads `○ Example · <the example's title>` and the title is no longer a row
of the body. A fifth example, `Hand off complex coding tasks`, asks
`/senior-dev Add retries with backoff to the HTTP client, with tests.`
beside `Start a conversation`, and a command in any example's request
wears the composer's chip. The keys line says `enter sets the limit` on
the limit row and `↑↓ moves` in place of `tab moves` everywhere; the tmux
suite's needle follows it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t, the panel loses its foot line, and the account's warning rides the frame's foot

`Models and spending` over `Keep these choices or change them.` is one
line, `Basic settings`, and the keys line is the row directly under it
rather than the foot of the form. The keys line no longer names the
example's arrows: the panel's own bottom edge carries them. The panel no
longer ends on `An illustration. Nothing here has run.`; its `Example`
label says that once. The low-credits and expired-key lines leave the
chat-model row and stand right-aligned on the frame's last row, where the
keys row carries the same warning outside the setup; the expired-key line
is shortened to fit any window the panel fits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…grey once answered, white until then

`→` on the last example is the first again, and `←` on the first the
last. Every row's name — the limit, the chat model, the review, the way
out — is painted by one rule: the accent while the focus is on it, dim
once enter has acted on it (the limit set, a model taken from the list,
the review shown), and the body ink until then. Opening the list and
leaving it with esc answers nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Each row was the catalog's friendly name with the exact id drawn under
the cursor's row, which read as a heading with a subheading and made the
one row a person could act on two rows tall. Each row is the id now, one
row per model; typing still finds a model by its friendly name and the
field above still shows it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t points at /settings

`Other settings use defaults · review` showed three settings and let
nobody change them — a door painted on a wall, and people pressed on it.
The form is three rows now: the limit, the chat model, the way out. Under
`Start a conversation` one dim line reads `Everything else is in
/settings`, with the command painted as the composer's chip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ut carries no loose enter

The limit's line ends `/budget changes it later.` and the chat model's
`/model changes it later.`, each command painted as the composer's chip
the way the note's /settings is. The dim `enter` at the right of `Start a
conversation` is gone: the keys line under the heading already says
`enter starts` when the focus is there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and never follow the focus

The panel followed the focused row, which read as it jumping about under
a person walking the form. It opens on the first example and turns to
the next every three seconds, round the ring; ←/→ browse by hand; any
key — typing, walking the rows, browsing, a press — holds the clock for
a full three seconds from that key; the screen-reader tier never turns
by itself. Each arriving example plays its demonstration once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n's ceilings

`?` on the Daily limit row now says what the two ceilings are and how
they meet: /budget 50 changes the day's and /budget none removes it;
/budget conversation 20 gives the open conversation a smaller ceiling of
its own and keeps it as the default for new ones; both hold at once and
whichever is reached first stops the work — the day's everything until
midnight, the conversation's just that conversation; the running turn
always finishes. Each /budget wears the composer's chip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on the list

TestModelNamesReadAsNamesAndKeepTheirLevel still expected the open list to
draw "DeepSeek V4 Flash", which the flat list of exact ids stopped doing on
purpose; it now asserts the two halves that hold — the field row and the
typed search speak the friendly name, every row of the open list is the
exact id — and leaves the flat shape itself to the test that owns it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the setup's prose catches up

The setup's foot said "esc to paste a new one" on every frame, while the keys
line two rows up said "esc skips setup" whenever the controls step stood
alone — a key already in the shell or the profile, only the controls asked.
The line is now chosen by the same rule as the keys line: with a connect
step before it, esc goes back there; standing alone, it names /connect as
the door to a new key. A test pins the alone case on the same frame.

The manual stops listing the review row's enter verbs (`shows them`, `goes
on`) and stops saying the setup shows memory, permissions and the countdown
under `Other settings`; both pages that explained esc on an expired key say
the conditional thing.

The comments the redesign left behind say what is true now: the example
turns on a clock and the focus never moves it, the panel stands above the
form, the edge label — not a foot line — is what says it is an illustration,
and `codeaf telemetry show` is spoken of as the command that went.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ZeroPoint95
ZeroPoint95 force-pushed the zeropoint95/onboarding-ux-cleanups branch from 297c81a to 1580bb7 Compare October 1, 2026 20:54
@ZeroPoint95
ZeroPoint95 marked this pull request as ready for review October 1, 2026 21:12
ZeroPoint95 and others added 8 commits October 2, 2026 09:26
…oted whole

The detail ran on past "whichever is reached first stops the work" into
what each ceiling holds and what the provider does, which is more than a
`?` on one row should say; it stops there now. Its commands are quoted
whole — '/budget 50', '/budget none', '/budget conversation 20' — so a
reader sees the amount is part of what is typed, and the second sentence
says a conversation's smaller ceiling is set by e.g. '/budget conversation
20'.

A quote is a word boundary for the command chip now, on either side:
prose that says '/budget 50' is naming the command and gets the chip on
its name, '/settings' is chipped without its closing quote, and a quoted
path is still a path. The manual mirrors the new wording.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Quote boundaries had reached the send scan, so asking about /task could start work.
Keep the original word boundaries for action; resting words and follow-up plain ranges use the transcript's quote-aware painting scan.
Leave quoted door names plain after an ordinary send and in guard/revive wrappers, while live tags retain their chips.
Scope the bash guard to action and draft/transcript paint; informational lines beginning with ! still chip known commands.
Cover straight and curly quotes, whole-message delivery, unchanged unquoted tags, ordinary-send transcript paint, informational ! lines and the manual answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g models

The engine flattened refusals into text and lost the expiry-triggered balance read.
Recognize that text beside provider status handling, and keep expired readings from moving untouched conversations between defaults.
Cover structured and flattened refusals, excluded statuses and direct services, and document both launch roads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep the no-match model list open with its row unanswered and its focus and model unchanged.
Hold example turns behind one timer, including a tick queued before the latest key.
Build the free-id set once per catalog pass and prove the 400-row order and membership; omit the wall-clock benchmark comparison.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d off

The settings row persisted off while the running process kept sending usage and exit events.
Close the existing process gate after a successful off write; enabling waits for startup to resolve all opt-outs.
Name replacement switches in the removed-command error and make the disclosure, hints and manual agree on timing, defaults and relay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The controls explanation exceeded a standalone manual read, and the change entry still claimed removed review and example-key behavior.
Split self-contained setup topics, add retrieval probes, and describe current credit, model-list and telemetry outcomes.
Use the required first-run title and describe only origin/dev-to-PR invalidations, including the empty model list and telemetry switches.
Split the empty-task-page answer too because the changed corpus ranking otherwise hid an existing probe; leave every existing probe unchanged.
Keep the standalone section-size test; drop the transient change-entry test and duplicated exact-sentence assertion.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The engine sends the session's unauthorized ending without its typed provider refusal, so the ordinary launch missed the fresh balance read.
Export one unauthorized sentence for both session spellings and match its trimmed prefix after the engine wire, under the existing refusal debounce.
Keep typed expiry recognition and the direct-service exclusion; prove the real 401 ending, arbitrary key-source tails, excluded statuses and unchanged conversation defaults.
Document that the read decides expiry and an invalid key leaves the previous reading alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A landing retained its sendable pool configuration through judging, so turning the settings switch off still allowed a later POST.
Resolve the live pool ladder before adding outgoing scores and at each HTTP request, including later batches and retries; keep local scores and leave older unsent rows pending.
Preserve the pool's existing behavior on source and test builds through its own configuration ladder.
Prove a switch changed inside the judge sends no new request, a switch changed during the first batch stops the next one, and ordinary pushes and quiet modes still work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AbirAbbas and others added 2 commits October 2, 2026 12:14
Name the finished-task foot in the standalone empty-page answer and shorten it to 1,945 characters without losing facts; keep every existing retrieval probe.
Remove the inaccurate claim that the previous telemetry page omitted the relay URL.
Update the existing engine-read and settings-off claims to describe the shared unauthorized prefix, the 30-second debounce and pool rows whose judging was already running.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Collaborator

Taking over: @ZeroPoint95, I walked the first run by hand on an empty home at 130x40, 120x24 and 80x24 (clicks on every row, wheel on the list, enter walking the form, ↓ enter enter sending, the panel clock and its hold), drove the low-credits and expired-key states against a local stub of the credits/key routes on both codeaf and codeaf chat --no-host, and checked every telemetry off switch against a local listener. Everything in the body held except the items below, which are fixed on the branch.

  • bc251605e tui3: What does '/task' do? started a task with the brief What does ' ' do? (quote boundaries reached the send scan). Quotes now count for painting only; the quoted sentence sends as prose and its /task is drawn plain.
  • f54715667 credits: an expired reading no longer moves an untouched conversation from the free default to the paid one; the engine text spelling of an expired 401 is recognised in provider.
  • 3138850d0 credits: on plain codeaf (engine road) a turn refused as expired never asked for a balance read, because the wire carries the session's your key was not accepted for this model ending rather than the typed refusal. That sentence is now one exported constant and the surface asks for the read on it (same 30 s debounce; the read decides).
  • 6d15cc327 tui3: enter with nothing matches closed the list, marked the row answered and moved on; it now stays. Every key armed a fresh 3 s timer (100 keys, 101 timers); one timer with a hold deadline now. The low-balance filter builds its free set once instead of rescanning the catalog per row.
  • d89822a36 telemetry: turning the /settings switch off kept the running process sending usage_delta and session_ended (seen on a local listener). Off now stops at once; on lands at the next start. codeaf telemetry … still exits 1 and now names CODEAF_TELEMETRY=off, DO_NOT_TRACK=1, the /settings switch and docs/TELEMETRY.md. The doc says counts are on by default and its test holds all five switches.
  • f070cb520 pool: a judge already running when the switch went off still posted its row; the pool now reads the live switches before recording outgoing rows and before each request.
  • 3a7e1fa2e, 8c9c9594e manual and change entry: the 6,600-character setup section is split into self-contained topics with probes; the entry drops the removed review row, the ←→ examples contradiction and the branch-only states, and its title is under 100 characters.
  • 544f8e07a merges today's dev.

Left for later, not changed here: a custom CODEAF_BASE_URL is still named OpenRouter, so a 401 saying "expired" from another OpenAI-compatible endpoint would show the OpenRouter line; switching telemetry off and back on mid-session resumes Model Pool rows before the restart the docs describe; crew/task endings do not ask for the balance read; the welcome hint still reads enter fills the box once the box is full.

Keep the caller's resolved pool configuration instead of re-reading the process environment.
Recheck the project and profile telemetry rows before recording or sending to honor live opt-outs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AbirAbbas

Copy link
Copy Markdown
Collaborator

One more: d9dab5c57 pool — the live off check added in f070cb520 re-resolved the whole pool config from the process environment, so under CI=true it refused every send and TestPoolPushPostsPendingRowsUnderTheInstallNonce went red on CI. It now re-reads only the two switches that can change while codeaf runs (the profile row and the project file) on top of the caller's own config. CI is green on d9dab5c57.

@AbirAbbas AbirAbbas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified and taken over; merging.

@AbirAbbas
AbirAbbas merged commit f86e4ae into dev Oct 2, 2026
9 checks passed
@AbirAbbas
AbirAbbas deleted the zeropoint95/onboarding-ux-cleanups branch October 2, 2026 17:19
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.

2 participants