Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions docs/okr/2026-03-30-inline-note-char-level-visual-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# OKR — Char-Level Visual Navigation for Wrapped Inline Note Editing

Date: 2026-03-30
Related track: B2 (follow-up to wrapped inline note visual cursor work)
Related context:
- issue #1 comment `#issuecomment-4150421927`
- existing intermediate fix: word-wrap-based visual movement (`B1`)

## Objective

Make wrapped inline note editing in `pi-ask-tool` follow the **actual visible rendered lines** so that `↑` and `↓` move the caret one on-screen line at a time, even in the presence of repeated spaces, reflow-sensitive content, and inline renderer-specific wrapping.

## Why This Exists

The current intermediate solution improves vertical movement by using `pi-tui` editor internals for visual-line movement. However, those internals are based on the editor's own `wordWrapLine` segmentation, which is not guaranteed to match the exact visual rows produced by `pi-ask-tool`'s inline option renderer.

That means the current state can still diverge from the user's actual visual expectation in cases like:
- repeated spaces
- wrap-sensitive phrase boundaries
- inline prefix/marker/separator width effects
- renderer-specific wrapping differences

This OKR defines the next step: movement based on **the rendered rows the user actually sees**, not only on editor-internal wrapped segments.

## Desired User Behavior

When an inline note is visually wrapped across multiple on-screen rows:
- `↑` moves the caret to the visible row above
- `↓` moves the caret to the visible row below
- movement preserves the same visible column when possible
- if the target row is shorter, the caret clamps to the nearest valid visible position
- behavior remains stable even when the text contains repeated spaces
- behavior matches what the user sees, not merely what the editor's internal word-wrap model predicts

## Key Results

1. **Rendered-row semantics**
- Inline note cursor movement uses the same row segmentation as the displayed inline note rendering.

2. **Char/cell-accurate vertical movement**
- `↑` / `↓` preserve visible column position as closely as possible on the target rendered row.

3. **Repeated-space robustness**
- Cases with multiple spaces do not cause obviously incorrect vertical jumps or misleading reflow behavior.

4. **Renderer parity**
- The mapping used for cursor movement matches the wrapping used by inline note display.

5. **Cross-screen consistency**
- The same behavior works in:
- `ask-inline-ui.ts`
- `ask-tabs-ui.ts`

6. **Regression safety**
- Add focused tests for repeated spaces, short/long row transitions, and rendered-row movement.

7. **Quality bar maintained**
- `npm run typecheck` passes
- targeted UI tests pass
- existing note editing behavior remains intact

## Acceptance Criteria

### Functional behavior
- `↑` / `↓` move by one **visible rendered row** at a time.
- Movement follows the actual wrapped rows shown in the inline note UI.
- `←` / `→` remain logical left/right movement.
- `Tab`, `Esc`, `Enter`, submit, cancel, and `Other` validation rules remain unchanged.

### Stability behavior
- Multiple spaces do not produce obviously incorrect row targeting.
- Movement from long row → short row clamps safely.
- Movement from short row → long row restores as much visible column as possible.
- Width changes produce a new correct rendered-row map before subsequent `↑` / `↓` movement.

### Scope behavior
- Single-question inline ask flow supports rendered-row movement.
- Tabbed ask flow supports rendered-row movement.
- No UX regression for inline note editing when no wrapping occurs.

## Key Scenarios

### Scenario 1 — Wrapped note with normal spacing
- A note wraps into three visible rows.
- `↑` / `↓` move between those rows predictably.

### Scenario 2 — Repeated spaces
- A note contains multiple spaces between words.
- The visible wrapping may shift in non-trivial ways.
- `↑` / `↓` still follow the actual rendered rows.

### Scenario 3 — Long row to shorter row
- Caret starts near the end of a long rendered row.
- Moving to a shorter row clamps to the nearest valid visible position.

### Scenario 4 — Short row back to longer row
- After clamping onto a shorter row, moving back should preserve as much intended visible column as possible.

### Scenario 5 — Same behavior in tabbed flow
- A wrapped inline note in tabbed ask uses the same movement semantics as the single-question flow.

### Scenario 6 — Resize-aware follow-up movement
- After width changes and rewrap, the next `↑` / `↓` action uses the new rendered-row map.

## Proposed Implementation Direction

## Core principle
Do not rely solely on `pi-tui`'s internal editor visual-line map for vertical navigation.
Instead, compute cursor movement from the **same rendered-row model used by the inline note UI**.

### Minimum architectural pieces
1. **Rendered-row map generator**
- Produces the same wrapped rows used by inline note rendering
- Tracks source index ranges for each rendered row

2. **Cursor position mapper**
- Converts current source cursor index → rendered row + visible column

3. **Rendered-row movement function**
- Moves to row - 1 / row + 1
- Preserves visible column where possible
- Clamps safely when needed

4. **Source-index resolver**
- Converts target rendered row + visible column back to source cursor index

### Suggested file shape
A focused helper module is preferred, for example:
- `src/ask-inline-note-map.ts`
- or equivalent naming

This helper should be the source of truth for:
- rendered inline note rows
- cursor-to-row mapping
- row-based cursor movement

### Important constraint
The wrapping logic used for cursor movement must match the wrapping logic used for rendering.
If these diverge, the UI will appear inconsistent even if the code "works."

## Non-goals

- Replacing the `Editor` implementation
- Replacing `pi-tui`
- Broad terminal layout framework work
- Solving unrelated tmux resize redraw issues in this same task
- Redesigning the inline note UX into a separate modal/editor

## Risks

- Source index ↔ rendered row/column mapping can become subtle around repeated spaces.
- ANSI/cursor rendering details must not leak into logical mapping assumptions.
- Width changes can invalidate preferred-column memory if not handled carefully.
- Single-question and tabbed flows could drift if the mapping helper is not shared.

## Definition of Done

This OKR is complete when:
- `↑` / `↓` in wrapped inline note editing follow actual visible rendered rows
- repeated-space cases behave consistently with on-screen expectations
- single-question and tabbed flows share the same movement semantics
- existing editing behavior remains intact
- tests cover the important rendered-row edge cases
- `npm run typecheck` and targeted tests pass

## Suggested Work Breakdown

1. Define a rendered-row map structure for inline notes.
2. Extract or unify wrapping behavior so rendering and movement share the same model.
3. Implement cursor-index → rendered-row/column mapping.
4. Implement rendered-row up/down movement.
5. Integrate into `ask-inline-ui.ts`.
6. Integrate into `ask-tabs-ui.ts`.
7. Add repeated-space and clamp behavior tests.
8. Verify behavior in real `pi` + tmux interaction.
154 changes: 154 additions & 0 deletions docs/okr/2026-03-30-inline-note-visual-cursor-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Plan — Visual Up/Down Navigation for Wrapped Inline Note Editing

Date: 2026-03-30
Related source: issue #1 comment `#issuecomment-4150421927`
Scope: wrapped inline note editing behavior in `ask-inline-ui.ts` and `ask-tabs-ui.ts`

## Summary

Wrapped inline note editing currently presents note text across multiple visual lines, but `↑` and `↓` behavior appears to follow the editor's logical/raw-text cursor model rather than the user's visual-line expectation. This creates a mismatch: users see multiple wrapped lines, but vertical cursor movement does not behave like moving one visible line up or down.

This work should be tracked separately from overflow/viewport scrolling because it changes **cursor movement semantics**, not just screen visibility.

## Objective

Make `↑` / `↓` during wrapped inline note editing move the caret by **one visible wrapped line** at a time, preserving user expectations for vertical navigation.

## Problem Statement

Today, inline note editing uses the underlying editor for text state and cursor state, while the displayed caret is rendered inside a wrapped presentation layer. The displayed text is visually multi-line, but vertical movement does not appear to respect visual lines. In practice this means:

- `↑` / `↓` can feel like moving through raw text positions instead of visible rows
- behavior becomes more confusing with multiple spaces and wrapping/reflow
- caret display may be accurate, but movement semantics still feel wrong

## Desired Behavior

When a note is visually wrapped into multiple on-screen lines:

- `↑` moves the caret to the line above
- `↓` moves the caret to the line below
- horizontal position should be preserved as much as possible
- if the target line is shorter, the target position clamps to the nearest valid column
- `←` / `→` keep their existing logical left/right behavior
- `Enter`, `Tab`, `Esc`, and submit/cancel behavior remain unchanged

## Acceptance Criteria

1. **Visual-line vertical navigation**
- In wrapped note editing, `↑` and `↓` move relative to visible lines rather than raw string positions.

2. **Preferred column preservation**
- Repeated `↑` / `↓` attempts to preserve the same visual column when moving between wrapped lines.

3. **Line-length clamping**
- When moving to a shorter line, the caret lands at the nearest valid position on that line.

4. **Whitespace robustness**
- Multiple spaces and awkward wrap boundaries do not produce obviously broken vertical movement.

5. **Parity across both UIs**
- Behavior is consistent in:
- `ask-inline-ui.ts`
- `ask-tabs-ui.ts`

6. **No regression in existing controls**
- `←` / `→`, typing, submit, cancel, and `Other` note requirements continue to work.

7. **Verification**
- Automated tests cover the main navigation scenarios.
- `npm run check` passes after the change.

## Non-goals

- Replacing the text editor implementation entirely
- Adding mouse support
- Solving viewport overflow/scrolling in this same task
- Implementing a full terminal text-layout engine

## Key Scenarios

### Scenario 1 — Two visual lines
- A note wraps into exactly two visible lines.
- `↓` from the first line moves to the second line.
- `↑` from the second line returns to the first line.

### Scenario 2 — Three visual lines with preferred column
- A note wraps into three lines.
- Moving from line 1 → line 2 → line 3 preserves column where possible.
- Moving back upward restores the same approximate column.

### Scenario 3 — Shorter target line
- The caret is near the end of a long line.
- `↓` moves to a shorter wrapped line.
- Caret clamps to the valid end position of the shorter line.

### Scenario 4 — Multiple spaces in content
- The note contains repeated spaces.
- Wrapping changes the visual layout.
- `↑` / `↓` still behave according to visible lines instead of feeling random.

### Scenario 5 — Same behavior in tabbed flow
- Edit a note in multi-question tab UI.
- Wrapped vertical navigation behaves the same as in the single-question flow.

### Scenario 6 — Width-sensitive rewrap
- Terminal width changes cause rewrap.
- Subsequent `↑` / `↓` still produce consistent visual movement under the new layout.

## Implementation Direction

### Core approach
Intercept `↑` / `↓` while inline note editing is active and translate them into a **visual-line-aware caret move** before updating the underlying editor cursor.

### Likely components needed
1. **Current note text**
2. **Current linear cursor index** from the editor
3. **Wrap mapping** for the current render width:
- visual line index
- start/end source indices per visual line
- visual column positions
4. **Preferred column state** for repeated vertical movement
5. **Cursor relocation function**:
- find current visual line
- target line = current ± 1
- compute target source index from preferred column
- clamp if needed
- update editor caret position

### Architectural note
This likely deserves a small dedicated helper module, for example:
- `ask-inline-visual-cursor.ts`
- or a similarly named utility for wrapped cursor navigation

That helper should stay separate from rendering-only helpers so the distinction remains clear:
- rendering helper = how wrapped text is shown
- visual cursor helper = how caret movement maps across wrapped lines

## Risks

- The underlying `Editor` may not expose enough direct cursor-control APIs, which could require adapter workarounds.
- Multiple spaces and wrapped presentation may complicate source-index ↔ visual-column mapping.
- Width changes may invalidate preferred-column assumptions unless handled carefully.
- Shared behavior across both inline and tabbed UIs could drift if not centralized.

## Definition of Done

This work is complete when:

- `↑` / `↓` in wrapped inline note editing behave like visual one-line-up / one-line-down movement
- behavior is consistent across single-question and tabbed flows
- awkward spacing/wrapping cases are covered by tests
- existing note-editing behavior remains intact
- `npm run check` passes

## Suggested Work Breakdown

1. Inspect `Editor` cursor-control capabilities.
2. Define the wrap-to-source-index mapping model.
3. Build a small helper for visual vertical cursor movement.
4. Integrate it into `ask-inline-ui.ts` while editing is active.
5. Integrate the same logic into `ask-tabs-ui.ts`.
6. Add focused tests for wrapped vertical navigation.
7. Run full verification.
8. Document the behavior if needed in README or issue notes.
Loading
Loading