* fix(web): add close button to dialog so modals are dismissable on mobile
The shared DialogContent had no close affordance — desktop users could
press Escape or click the overlay, but on mobile (no Escape key, dialog
spans calc(100vw-24px) leaving almost no tappable overlay) there was no
way to dismiss it. Add a DialogPrimitive.Close X button in the top-right,
fixing every dialog that uses this component at once.
* fix(web): reserve header space for dialog close button
Address review feedback: the absolutely-positioned close button overlaps
the top-right of every dialog. Long/breaking titles (e.g. DiffView's
break-all filename) could wrap underneath the 32px tap target. Add pr-12
to DialogHeader rather than padding DialogContent globally, so the title
row clears the button while body content (code blocks, diffs) keeps full
width.
* fix(web): localize dialog close button aria-label
Use the existing button.close locale string instead of a hardcoded
"Close" so screen-reader users get the label in their language (zh-CN: 关闭).
The Claude effort selector (New Session config + in-session composer) only
offered auto/medium/high/max, missing `low` and `xhigh` — yet `claude --effort`
actually accepts low/medium/high/xhigh/max. Add the two missing levels in both
places so the selector faithfully mirrors the CLI.
Extract the level list + labels into one shared constant
(@hapi/protocol: shared/src/effort.ts, mirroring CLAUDE_MODEL_PRESETS) so the
two UIs derive from a single source and can't drift again. No backend change:
the effort string is free-form end-to-end through to the --effort flag.
ultracode is intentionally excluded — it is a TUI-only /effort session setting,
not an --effort value (the CLI rejects `--effort ultracode`).
Closes#724
The previous policy pinned acceptance to a single past model version that
is no longer current SOTA, was not reliably detectable by reviewers, and
penalized contributors who honestly disclosed their tooling. Swap it for
a disclosure-only line — code is judged on merit, transparency is kept.
The "Default" model in NewSession sends no --model flag, so Claude CLI
picks its own default (e.g. Opus 4.7 [1m] on Pro accounts). The web
status bar then falls back to 200K - 10K headroom = 190K because the
Claude SDK path never plumbs the real per-model contextWindow through
to the wire-level `modelContextWindow`, unlike the ACP/Codex backends.
Fix the gap in three places:
- cli/src/claude/sdk/types.ts: declare optional `modelUsage` on
SDKResultMessage to surface what Claude CLI already emits
(`modelUsage[<model>].contextWindow`).
- cli/src/claude/utils/sdkToLogConverter.ts: on system.init, capture
the resolved model name (full form with `[1m]` suffix) and derive
an initial contextWindow from the suffix. On every assistant
message, inject the cached contextWindow into `usage.context_window`
when absent. On result, refine the cache with the authoritative
value from `modelUsage`.
- web/src/chat/normalizeAgent.ts: forward `context_window` through
the assistant usage normalization, so the existing reducer path
(reducer.ts:175 → StatusBar.tsx:175) can render the real window.
Closes#719.