Problem or Motivation
OpenWork's chat composer (FreeFormInput) gives no feedback about how much
you've written. The composer toolbar already surfaces a context-usage
indicator (tokens used / total), a model picker, a thinking-level picker and an
attach control — but there is no length feedback for the draft itself. When
composing a long prompt (a spec, a big paste, a multi-paragraph instruction) you
have no glanceable sense of its size.
Comparable desktop composers and editors ship exactly this. The Codex desktop
composer and editor status bars (VS Code, Google Docs, the Claude/ChatGPT desktop
message boxes) all show a live word / character count of what you're
currently typing. It is a small, universally-expected affordance that OpenWork's
otherwise feature-rich composer toolbar is missing.
Proposed Solution
Add a small, unobtrusive draft count indicator to the composer toolbar
(left-of-center, next to the attach / working-directory badges, mirroring their
!compactMode rule):
- Shows the word count of the current draft inline (e.g.
Words: 12), only
while the composer has non-whitespace text — so the empty composer stays clean.
- A hover tooltip breaks down the full stats: Words, Characters, and
Lines.
- Counts update live on every keystroke (derived from the existing controlled
input text — no extra state).
- Hidden in
compactMode (mirroring the context-usage / thinking / model
controls) and hidden entirely when the draft is empty.
The counting itself (words / characters / lines) lives in a small pure,
unit-tested module so its semantics (Unicode-aware character count,
whitespace-collapsing word count, newline-based line count) are locked down
without a DOM.
Feasibility
Classification: pure frontend — candidate. No qwen-code backend involvement.
- The composer already stores the draft as a plain-text string in its controlled
input state (FreeFormInput.tsx); the indicator is pure derived state over
that value — no new IPC, no main-process change, no persistence.
- The indicator reuses the existing composer-toolbar layout (it slots between the
left badge group and the flex spacer) and the shared Tooltip primitives.
- Three new i18n keys (
chat.composerWords, chat.composerCharacters,
chat.composerLines) added to all locales, using a plural-neutral
label form ("Words: {{count}}") so no per-locale plural categories are
needed.
Alternatives Considered
- Character count only: simpler, but word count is the more meaningful metric
for prose prompts; showing words inline and the full breakdown (words /
characters / lines) on hover covers both without clutter.
- A live token count of the draft: more useful in theory but requires a
tokenizer that matches the backend model — that is backend-coupled and belongs
with the existing context-usage indicator, not a pure-frontend counter.
- Always-visible (even when empty): would clutter the empty composer, which
the app deliberately keeps minimal; showing the indicator only when there's
content matches the toolbar's other content-dependent affordances.
Additional Context
Feasibility verified against apps/electron: the composer's input is a plain
string (coerceInputText), the RichTextInput is a hand-rolled contentEditable
that fires onChange with plain text on every input event, and the toolbar's
bottom row already has a left badge group + flex spacer where the indicator
slots in cleanly.
Acceptance criteria (CDP e2e assertion)
A new e2e/assertions/composer-count.assert.ts drives the real built app over CDP
entirely in the draft (no-session) state — no seeded conversation and no backend:
- The composer's contenteditable renders and, while empty, the count
indicator ([data-testid="composer-count"]) is absent — proving it stays
hidden for an empty draft.
- Real typing (via CDP
Input.insertText into the focused editor) of
hello world makes the indicator appear with data-word-count="2" and
data-char-count="11" — proving it reflects actual typed content.
- Appending more text updates the counts live (word count increases, char count
increases) — proving it is reactive per keystroke, not a one-shot render.
- Selecting-all and deleting the draft makes the indicator disappear again —
proving it reacts to the draft becoming empty.
In addition, the pure counting module ships with unit tests
(composer-count.test.ts) covering empty / whitespace-only input, single vs.
multiple words, collapsed internal whitespace, multi-line line counting, and
Unicode/emoji character counting.
Part of the autonomous desktop-feature loop (loop-bot).
Problem or Motivation
OpenWork's chat composer (
FreeFormInput) gives no feedback about how muchyou've written. The composer toolbar already surfaces a context-usage
indicator (tokens used / total), a model picker, a thinking-level picker and an
attach control — but there is no length feedback for the draft itself. When
composing a long prompt (a spec, a big paste, a multi-paragraph instruction) you
have no glanceable sense of its size.
Comparable desktop composers and editors ship exactly this. The Codex desktop
composer and editor status bars (VS Code, Google Docs, the Claude/ChatGPT desktop
message boxes) all show a live word / character count of what you're
currently typing. It is a small, universally-expected affordance that OpenWork's
otherwise feature-rich composer toolbar is missing.
Proposed Solution
Add a small, unobtrusive draft count indicator to the composer toolbar
(left-of-center, next to the attach / working-directory badges, mirroring their
!compactModerule):Words: 12), onlywhile the composer has non-whitespace text — so the empty composer stays clean.
Lines.
inputtext — no extra state).compactMode(mirroring the context-usage / thinking / modelcontrols) and hidden entirely when the draft is empty.
The counting itself (
words/characters/lines) lives in a small pure,unit-tested module so its semantics (Unicode-aware character count,
whitespace-collapsing word count, newline-based line count) are locked down
without a DOM.
Feasibility
Classification: pure frontend — candidate. No qwen-code backend involvement.
inputstate (FreeFormInput.tsx); the indicator is pure derived state overthat value — no new IPC, no main-process change, no persistence.
left badge group and the flex spacer) and the shared
Tooltipprimitives.chat.composerWords,chat.composerCharacters,chat.composerLines) added to all locales, using a plural-neutrallabel form (
"Words: {{count}}") so no per-locale plural categories areneeded.
Alternatives Considered
for prose prompts; showing words inline and the full breakdown (words /
characters / lines) on hover covers both without clutter.
tokenizer that matches the backend model — that is backend-coupled and belongs
with the existing context-usage indicator, not a pure-frontend counter.
the app deliberately keeps minimal; showing the indicator only when there's
content matches the toolbar's other content-dependent affordances.
Additional Context
Feasibility verified against
apps/electron: the composer'sinputis a plainstring (
coerceInputText), theRichTextInputis a hand-rolledcontentEditablethat fires
onChangewith plain text on everyinputevent, and the toolbar'sbottom row already has a left badge group + flex spacer where the indicator
slots in cleanly.
Acceptance criteria (CDP e2e assertion)
A new
e2e/assertions/composer-count.assert.tsdrives the real built app over CDPentirely in the draft (no-session) state — no seeded conversation and no backend:
indicator (
[data-testid="composer-count"]) is absent — proving it stayshidden for an empty draft.
Input.insertTextinto the focused editor) ofhello worldmakes the indicator appear withdata-word-count="2"anddata-char-count="11"— proving it reflects actual typed content.increases) — proving it is reactive per keystroke, not a one-shot render.
proving it reacts to the draft becoming empty.
In addition, the pure counting module ships with unit tests
(
composer-count.test.ts) covering empty / whitespace-only input, single vs.multiple words, collapsed internal whitespace, multi-line line counting, and
Unicode/emoji character counting.
Part of the autonomous desktop-feature loop (
loop-bot).