Skip to content

feat(writing): editable min/max for the custom Length range#653

Merged
FuJacob merged 3 commits into
mainfrom
feat/editable-length-range
Jun 9, 2026
Merged

feat(writing): editable min/max for the custom Length range#653
FuJacob merged 3 commits into
mainfrom
feat/editable-length-range

Conversation

@FuJacob

@FuJacob FuJacob commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Makes the custom Length range directly editable. The old "Custom range…" UI was two side-by-side steppers with static Min: N / Max: N labels, so values could only be nudged one at a time. This replaces them with stacked Minimum and Maximum rows, each a numeric text field paired with up/down arrows, so a user can type a value or step it. The sensible bounds are unchanged: both rows commit through setCustomWordCountRange, which clamps to [1, 50] and keeps Max >= Min.

Validation

swiftlint lint --quiet
# exit 0

xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build \
  -derivedDataPath build/DerivedData
# ** BUILD SUCCEEDED **

xcodebuild ... build-for-testing CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO
# ** TEST BUILD SUCCEEDED **

UI: rendered the real WritingPaneView in Custom-range mode via an offscreen NSHostingView (so the AppKit-backed field/stepper rasterize) and confirmed the layout: a "Minimum" row showing an editable 5 with arrows above a "Maximum" row showing 48 with arrows, then the existing token-budget caption. I did not launch the full menu-bar app, to avoid its accessibility / input-monitoring permission flow.

Linked issues

None.

Risk / rollout notes

  • UI only, scoped to WritingPaneView.swift. No settings schema, persistence, or pbxproj change.
  • Clamping is unchanged: the fields reuse the existing setCustomWordCountRange / SuggestionWordRange.clamped(low:high:) path, so typed values are bounded to [1, 50] with Max snapped up to Min on commit, exactly as the steppers were. The .number field commits on Return / focus loss, so the clamp never fights intermediate keystrokes.
  • Independent of feat(appearance): ghost text size control and live preview #651 (different files); the two PRs can merge in any order.

Greptile Summary

Replaces the old pair of side-by-side Stepper controls in the Custom Length UI with two stacked LabeledContent rows, each containing a numeric TextField paired with a Stepper via a shared Binding<Int>. Users can now type a value directly or nudge it with arrows; all writes still route through setCustomWordCountRange, preserving the existing clamp-to-[1, 50] and Max ≥ Min invariants.

  • Adds a private wordCountField(value:label:) helper annotated @ViewBuilder that renders the TextField+Stepper pair, applies .accessibilityLabel to both controls, and is reused for Minimum and Maximum rows.
  • Removes the old HStack of two raw Stepper views and replaces it with two LabeledContent rows; the token-budget caption below is preserved in the same conditional block.

Confidence Score: 5/5

Safe to merge — purely a UI-layer change in a single file, no schema, persistence, or model logic touched.

All value writes still funnel through the unchanged setCustomWordCountRange path, so the clamp-to-[1,50] and Max >= Min invariants are preserved for both typed and stepped input. The TextField with .number format defers binding updates until Return/focus loss, so intermediate keystrokes never fight the clamp. No test, migration, or configuration surface is affected.

No files require special attention.

Important Files Changed

Filename Overview
Cotabby/UI/Settings/Panes/WritingPaneView.swift Replaces side-by-side Steppers with stacked LabeledContent + TextField/Stepper rows; adds wordCountField helper; accessibility labels applied correctly; clamping unchanged via existing setCustomWordCountRange path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User interaction] --> B{Input method}
    B -->|Type in TextField| C[Intermediate keystrokes buffered<br/>binding NOT called yet]
    B -->|Press Stepper arrow| D[Stepper calls binding.set<br/>immediately]
    C -->|Return / focus loss| E[TextField commits<br/>binding.set called]
    D --> F[customLowBinding or customHighBinding setter]
    E --> F
    F --> G[setCustomWordCountRange<br/>low:high:]
    G --> H{Clamp logic}
    H -->|low < minimumWord| I[low = minimumWord]
    H -->|high > maximumWord| J[high = maximumWord]
    H -->|high < low| K[high = low]
    I --> L[Persist to SuggestionSettingsModel]
    J --> L
    K --> L
    H -->|values in range| L
    L --> M[UI re-renders with clamped values]
Loading

Reviews (2): Last reviewed commit: "fix(a11y): give the custom Min/Max word-..." | Re-trigger Greptile

Replace the cramped side-by-side steppers with stacked Minimum and Maximum
rows, each an editable numeric field paired with up/down arrows. Typed and
stepped values both commit through setCustomWordCountRange, so they stay
clamped to [1, 50] with Max >= Min. Users can now type a value directly
instead of only nudging the arrows, and the layout reads more clearly.
Comment thread Cotabby/UI/Settings/Panes/WritingPaneView.swift
FuJacob added 2 commits June 9, 2026 00:16
Addresses Greptile feedback: LabeledContent's visible title isn't applied
to the controls, so the TextField and Stepper were announced unnamed.
Thread a label through wordCountField so Min and Max each get a distinct
accessibility label.
@FuJacob FuJacob merged commit ea35fd0 into main Jun 9, 2026
4 checks passed
@FuJacob FuJacob deleted the feat/editable-length-range branch June 9, 2026 07:18
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