API ergonomics pass 1: Line.FromText, string overloads, AllowBack, secret-default mask (v0.2.0-rc.1)#3
Merged
Merged
Conversation
Open the change that closes three of the five §14.4 ergonomics gaps surfaced by the dmon-wizard port: Line.FromText + string-accepting overloads (gap 5), opt-in AllowBack on Select/Choice dialogs (gap 1), and secret-default masking on the input dialog (gap 3). Surface-only; no breaking changes; all additions or opt-in flags. Targets dcli 0.2.0-rc.1. Also folds in a small docs task (§6.7) to refine the repo CLAUDE.md's "Where to look for historical context" subsection to match the canonical wording recommended by the personal devlog skill (covers both in-flight + archived DEVLOGs; the form shipped in PR #2 only covered the archived case). Scaffolds DEVLOG.md inside the change directory per the personal devlog skill's convention (`~/.claude/skills/devlog/SKILL.md`). First exercise of that skill end-to-end. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 1.1 Add `public static Line FromText(string text, Style? style = null)`
to src/Dcli/Line.cs; returns a Line with a single Segment(text, style ?? default).
- 1.2 XML docs on FromText describe it as the canonical short form for
label-only Lines, and explicitly state that no implicit string→Line
conversion is defined (deliberate API choice; design.md Decision 1).
- 1.3 StyledTextTests gains 5 facts covering default style, explicit style,
empty string, multi-rune round-trip, and structural-equality with the
manual `new Line(new[]{ new Segment(text) })` form.
Gates: build 0 warnings, 693 tests green (688 baseline + 5),
dotnet format clean, openspec validate --strict clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tion 2) - 2.1 Add `void Append(string text)` to `IScrollback` in src/Dcli/ITerminal.cs. - 2.2 Implement `Append(string)` in ScrollbackSurface as a null-guarded forwarder to `Append(Line.FromText(text))`. - 2.3 Add `InputRequest(string? prompt, string? Default = null, bool IsSecret = false)` secondary ctor chaining to the existing `Line?` primary. - 2.4-2.6 Add `IReadOnlyList<string>` and `params string[]` ctors to SelectRequest, MultiSelectRequest, ChoiceRequest. All route items through `Line.FromText` with default style, preserving the existing `Title`/`Prompt` as `Line?`. Null-guards run before LINQ materialisation. - 2.7 FacadeTests gains a §2 round-trip section: each new overload is asserted equal to the canonical `Line` form via `Line` sequence equality, with one non-ASCII case (`"héllo🦊"`) covering the forwarding path. - 2.8 FakeTerminalTests' tier-A FakeScrollback implements the new `Append(string)` and asserts string/Line forms record identically. - 2.9 No `implicit operator` exists anywhere in src/; the doc seam stays on `Line.FromText` (added in §1). File layout deviation noted in DEVLOG: tasks.md named per-record files that don't exist (IScrollback lives in ITerminal.cs; all four request records live in DialogRequests.cs); implemented in the real files. `new InputRequest(null)` is ambiguous (documented behaviour); the natural `new InputRequest()` and the explicit `(string?)null` / `(Line?)null` forms are unambiguous and covered by tests. Gates: build 0 warnings, 702 tests green (+9 over §1), format clean, openspec validate --strict clean, `grep -rn 'implicit operator' src/` empty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…quest (section 3) - 3.1 SelectRequest gains `bool AllowBack = false` as a third positional parameter (additive, backward-compatible). String-accepting secondary ctors from §2 forward it. - 3.2 ChoiceRequest gains the same `AllowBack` parameter and secondary-ctor forwarding. - 3.3 Dialog.HandleKey gains a new Back rule between Escape and arrow nav: Backspace fires `OverlayCloseKind.Back` when `_allowBack && !_hasMoved && _filterText.Length == 0`. The `_filterText.Length == 0` guard defends against any future widening of `TypeToFilter` to Select/Choice. - 3.4 The ↑/↓ arms now flip `_hasMoved = true` before MoveUp/MoveDown so Backspace post-movement is suppressed for the rest of the overlay session. - 3.5 `OverlayCloseKind` gains a `Back` member. `Terminal.OpenModalAsync` replaces the Submit/Cancel ternary with a switch expression: Submit → buildResult, Back → DialogResult(Back, default), default → Cancelled (covers both `Cancel` and `null` defensively). `SelectAsync`/`ChoiceAsync` forward `req.AllowBack` into the Dialog ctor. `MultiSelectAsync` does NOT pass `allowBack` — MultiSelect cannot return Back. - 3.6 DialogSelectionTests gains a §3 block: AllowBack=true + Backspace-at-empty produces Back; AllowBack=true after ↓ is a no-op (dialog stays open; Esc produces Cancelled); AllowBack=false (default) ignores Backspace. - 3.7 New ChoiceDialogTests.cs with the equivalent three Choice tests. - 3.8 Reflection test pins `MultiSelectRequest` deliberately omitting AllowBack (Decision 4). - DialogOutcome.Back XML doc updated: no longer "Not produced by any v1 dialog" — now produced under opt-in AllowBack=true. Gates: build 0 warnings, 709 tests green (+7 over §2), format clean, openspec validate --strict clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ection 4) Spec/code-reality finding: the secret-default leak Decision 5 defends against does not actually exist in current code. `InputDialog.Render` runs `_isSecret ? MaskRows(rows) : rows` unconditionally on every paint since the file was introduced (commit 394c9ba, §12 of core-rendering-architecture). The proposal/design's premise that "bullets only kick in once the user edits" is inaccurate; the seeded default has always been masked on first paint via the existing width-preserving MaskRows path. Decision 5's "reuse the existing edit-detection flag (for InputChanged emission)" guidance is also moot — no such flag exists and no InputChanged event is emitted. The spec scenarios remain correct as behavioural requirements; §4 work therefore degenerates to: - 4.1+4.2 Introduce a single sticky `_userEdited` flag on InputDialog (false initially, flipped to true on the three buffer-mutating operations in HandleKey: printable-rune insert, Backspace, Delete). Caret-only moves and Enter/Escape do NOT flip it. The constructor's `_buffer.SetText(default)` does NOT flip it. The flag is sticky: once true, never reset (even if the buffer is shrunk back to its original text). Exposed via `internal bool UserEdited => _userEdited` for test inspection. - 4.3 No change to `Text` — it always returns the real buffer contents. - 4.4 Five InputDialogTests pin the spec scenarios: masked-on-first-paint (a regression guard, not a bug repro), masked-before-first-edit, Text-returns-real-default, one-edit-then-revert-sticky semantics, and the existing IsSecret round-trip. - 4.5 Non-secret default regression guard test. Future trap recorded in DEVLOG: paste / history-recall (mentioned in `specs/fixed-region/spec.md` "Owned input editor" as edit triggers) are not implemented today, so they don't currently need to flip `_userEdited`. If either gets added later, they must. Gates: build 0 warnings, 714 tests green (+5 over §3), format clean, openspec validate --strict clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…n 5) - 5.1 WizardRenderer.cs: 12 → 10 LineBuilder sites. Two label-only Select item builders in RenderChooseOneAsync/RenderChooseManyAsync replaced with Line.FromText. Remaining 10 are genuinely styled (Bold/Dim/Fg/multi-segment). - 5.2 Dcli.Demo/Program.cs: 43 → 37 LineBuilder sites. Six label-only replacements — two via the new Scrollback.Append(string) overload, four via Line.FromText in a MultiSelect items array. Remaining sites are styled compositions. - 5.3 Option A: AllowBack=true is now set on the ChooseOne Select dialog in WizardRenderer; DialogOutcome.Back maps to WizardStepOutcome.Back. WizardEngine.cs already routed WizardStepOutcome.Back to the previous step, so no engine changes were required — the affordance is live. Future ergonomics-pass-2 candidates recorded in DEVLOG (Line.Bold(s) / Line.Dim(s) / Line.Fg(s, color) shorthands would shrink another ~10+ sites in the same files; out of scope for pass-1). Gates: build 0 warnings, 714 tests green (unchanged — samples only), format clean, openspec validate --strict clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ck (section 6)
- 6.1 dotnet build -c Release — 0 warnings, 0 errors.
- 6.2 dotnet test -c Release — 714 tests green (688 baseline + 26 new
across §§1-4: 5 Line.FromText, 9 facade/fake string overloads,
7 AllowBack, 5 secret-default).
- 6.3 dotnet format --verify-no-changes — clean.
- 6.4 openspec validate api-ergonomics-pass-1 --strict — valid.
- 6.5 Version bumped 0.1.0-rc.1 → 0.2.0-rc.1 in Dcli.csproj and
Dcli.Testing.csproj (additive minor — every change in this proposal
is an overload or opt-in flag, no breaking changes).
- 6.6 dotnet pack -c Release produced:
- dcli.0.2.0-rc.1.nupkg + dcli.0.2.0-rc.1.snupkg
- dcli.testing.0.2.0-rc.1.nupkg + dcli.testing.0.2.0-rc.1.snupkg
- 6.7 CLAUDE.md "Where to look for historical context" subsection
updated to the canonical devlog-skill wording from
~/.claude/skills/devlog/SKILL.md ("also keep" / "inside the change
directory" instead of "may also keep" / "at the change's root"),
asserting the convention rather than presenting it as optional.
- 6.8 DEVLOG completed: row-per-section status table, deviations recorded
(the §2 file-layout deviation, §4's bug-doesn't-exist finding, §5's
next-pass ergonomics candidates), resume-point shipped stamp.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ore archive
- Fold the change's delta specs into the main openspec/specs/:
* styled-text/spec.md gains the ADDED requirement "Convenience construction
from plain strings" (Line.FromText factory + string overloads on
IScrollback.Append and *Request records; no implicit string → Line).
* fixed-region/spec.md's "Awaitable modal dialogs" requirement is replaced
to add the AllowBack opt-in flag on Select/Choice (Backspace-at-empty);
MultiSelect and Input explicitly do NOT expose AllowBack.
* fixed-region/spec.md's "Owned input editor" requirement is replaced to
add secret-default masking (mask the seeded Default until first edit when
IsSecret=true; Submit returns the real string regardless).
- Freeze the DEVLOG to shipped framing per ~/.claude/skills/devlog/SKILL.md:
drop the in-flight banner, replace "How to resume" with "Final state at
archive", and turn the resume-point pointer into a shipped stamp citing
final commit f3ac49b, archive date 2026-05-28, and the published NuGet
artifacts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ass-1 All six sections shipped. Delta specs already synced into main specs in the preceding commit. DEVLOG frozen with the shipped stamp. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the three §14.4 ergonomics gaps surfaced by the dmon-wizard port (memory:
section14-api-ergonomics-findings):Line.FromText(string, Style?)factory — replaces thenew LineBuilder().Text(s).Build()ceremony for label-only strings. Explicitly no implicitstring → Lineconversion.IScrollback.Append(string), plusInputRequest/SelectRequest/MultiSelectRequest/ChoiceRequestaccept string/params string[]/IReadOnlyList<string>items.AllowBackflag onSelectRequest/ChoiceRequest— opt-in (defaultfalse); Backspace at the empty initial position producesDialogOutcome.Back.MultiSelectRequestdeliberately omits this for now (ambiguous semantics at a multi-toggle position).InputDialog— regression-pinned. See "Notable findings".Line.FromText/ string overloads applied to label-only sites; a liveAllowBack=truestep wired inWizardRenderer(no engine change needed —WizardEngine.csalready handledWizardStepOutcome.Back).0.1.0-rc.1 → 0.2.0-rc.1inDcli.csproj+Dcli.Testing.csproj. NuGet artifactsdcli.0.2.0-rc.1.{nupkg,snupkg}+dcli.testing.0.2.0-rc.1.{nupkg,snupkg}produced cleanly.No breaking changes. Every public addition is an overload or an opt-in flag; existing call sites continue to compile and behave identically.
OpenSpec change archived as
openspec/changes/archive/2026-05-28-api-ergonomics-pass-1/. Delta specs synced into the mainopenspec/specs/{styled-text,fixed-region}/spec.md. DEVLOG frozen with the shipped stamp.Notable findings (logged in DEVLOG)
InputDialog.Renderhas unconditionally masked_isSecret=truerows via the width-preservingMaskRowspath since the file was introduced incore-rendering-architecture(394c9ba). The proposal's premise ("bullets only kick in once the user edits") anddesign.mdDecision 5's assumption that_userEditedalready existed forInputChangedemission were both inaccurate about the as-shipped code. The spec's behavioural scenarios still hold and are now pinned by regression tests; a sticky_userEditedflag was introduced as a single source of truth so the spec's edit-state vocabulary has a code-level seam.IScrollbacklives inITerminal.cs, all four request records inDialogRequests.cs(not per-record files). Implemented in the real files; not splitting them was a Decision-6 scope call.Line.Bold(s)/Line.Dim(s)/Line.Fg(s, color)single-style shorthands would shrink another ~10+ sites inWizardRenderer.cs+Dcli.Demo/Program.cs. Out of scope for pass-1; recorded in DEVLOG "Open follow-ups".Test plan
Commits
10 commits on `change/api-ergonomics-pass-1` (one per OpenSpec section + DEVLOG bookkeeping + sync + archive):
```
50c4456 openspec: archive api-ergonomics-pass-1 → 2026-05-28-api-ergonomics-pass-1
2ddfa81 openspec(api-ergonomics-pass-1): sync delta specs + freeze DEVLOG before archive
f3ac49b chore(api-ergonomics-pass-1): backfill §6 commit hash in DEVLOG
a9c64e1 chore(api-ergonomics-pass-1): validation, version bump 0.2.0-rc.1, pack (section 6)
3bde5fd feat(api-ergonomics-pass-1): demo ports + live AllowBack step (section 5)
9cbd883 feat(api-ergonomics-pass-1): secret-default masking in InputDialog (section 4)
fb90d34 feat(api-ergonomics-pass-1): AllowBack flag on SelectRequest/ChoiceRequest (section 3)
55cd4de feat(api-ergonomics-pass-1): string-accepting consumer overloads (section 2)
3dd518c feat(api-ergonomics-pass-1): Line.FromText factory (section 1)
b5619a8 openspec: propose api-ergonomics-pass-1 + scaffold DEVLOG
```
🤖 Generated with Claude Code