Skip to content

feat: add keyboard navigation to Breadcrumbs widget#2002

Open
jainiksha wants to merge 6 commits into
Karanjot786:mainfrom
jainiksha:feature/breadcrumb-keyboard-navigation-1989
Open

feat: add keyboard navigation to Breadcrumbs widget#2002
jainiksha wants to merge 6 commits into
Karanjot786:mainfrom
jainiksha:feature/breadcrumb-keyboard-navigation-1989

Conversation

@jainiksha

@jainiksha jainiksha commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Description

Summary

Adds keyboard navigation support to the Breadcrumbs widget, allowing users to navigate between breadcrumb items using the keyboard and trigger selection callbacks.

Changes Made

  • Added onSelect callback support.
  • Added keyboard navigation using Left and Right arrow keys.
  • Added Enter key support to activate the selected breadcrumb.
  • Updated highlighting to reflect the currently selected breadcrumb.

Why

This improves accessibility and provides a more interactive navigation experience for terminal applications.

Closes #1989

Summary by CodeRabbit

  • New Features
    • Added keyboard-friendly breadcrumb selection with left/right navigation and Enter to activate a segment.
    • Introduced workspace support for saving, switching, restoring, and clearing named layouts.
    • Updated text input behavior for smoother editing, suggestion handling, and cursor movement.
  • Bug Fixes
    • Improved breadcrumb highlighting so the active item matches the current selection.
    • Refined text input rendering and editing limits for more consistent on-screen behavior.
  • Tests
    • Added coverage for workspace saving, loading, switching, removal, and clearing.

@jainiksha jainiksha requested a review from Karanjot786 as a code owner July 3, 2026 16:59
@github-actions github-actions Bot added type:feature +10 pts. New feature. area:widgets @termuijs/widgets type:testing +10 pts. Tests. labels Jul 3, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

Hi @jainiksha 👋

Star this repo before your PR merges.

Why? GSSoC 2026 contributors who star get priority review and points credit. After you star, push any commit (or re-run this check). The needs-star label lifts automatically.

Thanks for your contribution to TermUI.

@github-actions github-actions Bot added the needs-star PR author has not starred the repo. label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Breadcrumbs widget gains keyboard-driven selection with an onSelect callback. TextInput's key handling and rendering pipeline are substantially refactored. A new Workspace module provides named layout save/load/switch/persist operations, exported publicly alongside a narrowed SplitPane type export.

Changes

Breadcrumb Keyboard Navigation

Layer / File(s) Summary
Selection state and key handling
packages/widgets/src/display/Breadcrumbs.ts
Adds onSelect option, _selectedIndex/_onSelect fields, a handleKey method for left/right/enter navigation, and updates render logic to highlight based on _selectedIndex instead of the last visible segment.

Estimated code review effort: 2 (Simple) | ~12 minutes

TextInput Refactor

Layer / File(s) Summary
Setup and constructor wiring
packages/widgets/src/input/TextInput.ts
Reformats imports and field initialization for placeholder, mask, maxLength, vim mode, and suggestions; rewires key event handling and the value setter.
Editing operations and cursor movement
packages/widgets/src/input/TextInput.ts
Updates insertChar/deleteBack guards and autocomplete resets, reformats cursor clamping, and updates submit/clear to trigger _onChange.
Vim and non-vim key dispatch
packages/widgets/src/input/TextInput.ts
Rewrites vim normal/insert mode mappings and replaces non-vim handling with a switch-based dispatcher.
Simplified rendering
packages/widgets/src/input/TextInput.ts
Rewrites _renderSelf with grapheme-based slicing/scrolling, simplified cursor placement, and forEach-based suggestion rendering.

Estimated code review effort: 4 (Complex) | ~55 minutes

Workspace Layout Persistence Module

Layer / File(s) Summary
Types and in-memory management
packages/widgets/src/layout/Workspace.ts
Defines WorkspaceLayout, WorkspaceStorage, WorkspaceOptions, and a Workspace class with Map-based save/load/switch/list/remove/clear methods.
Persistence via WorkspaceStorage
packages/widgets/src/layout/Workspace.ts
Implements save()/restore() to serialize/deserialize layouts through a WorkspaceStorage adapter.
Public exports and tests
packages/widgets/src/index.ts, packages/widgets/src/layout/Workspace.test.ts
Exports Workspace and its types, narrows the SplitPane type export to remove SplitDirection, and adds a Vitest suite covering core Workspace behaviors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Breadcrumbs
  participant OnSelectCallback

  User->>Breadcrumbs: handleKey(left/right)
  Breadcrumbs->>Breadcrumbs: update _selectedIndex
  User->>Breadcrumbs: handleKey(enter)
  Breadcrumbs->>OnSelectCallback: onSelect(index, label)
Loading
sequenceDiagram
  participant Caller
  participant Workspace
  participant WorkspaceStorage

  Caller->>Workspace: save(storage)
  Workspace->>WorkspaceStorage: save(name, JSON.stringify(layouts))
  Caller->>Workspace: restore(storage)
  Workspace->>WorkspaceStorage: load(name)
  WorkspaceStorage-->>Workspace: data or null
  Workspace->>Workspace: parse and replace layouts map
Loading

Possibly related PRs

  • Karanjot786/TermUI#748: Extends the same Breadcrumbs widget with _selectedIndex/handleKey navigation and onSelect callback logic.
  • Karanjot786/TermUI#1639: Both introduce a Workspace abstraction with save/restore-style layout persistence.
  • Karanjot786/TermUI#1698: Both modify the SplitPane type re-exports in packages/widgets/src/index.ts.

Suggested labels: level:intermediate

Suggested reviewers: Karanjot786

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is missing most required template sections, including package, change type, checklist, screenshots, and reviewer notes. Add the missing template sections: Which package(s), Type of Change, Checklist, screenshots/notes, and any required GSSoC details.
Linked Issues check ⚠️ Warning The PR adds keyboard navigation, but it does not implement clickable breadcrumb items or router integration requested by #1989. Add clickable breadcrumb item handling and router integration, or narrow the issue scope if those features are out of this PR.
Out of Scope Changes check ⚠️ Warning The PR includes unrelated Workspace and TextInput changes, plus export adjustments, that are outside the breadcrumb navigation issue. Split unrelated Workspace/TextInput/export refactors into separate PRs and keep this change focused on Breadcrumbs.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: adding keyboard navigation to the Breadcrumbs widget.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.1)
packages/widgets/src/input/TextInput.ts

File contains syntax errors that prevent linting: Line 33: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 44: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 48: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 48: expected a semicolon to end the class property, but found none; Line 48: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 119: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 122: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '; Line 134: Expected an identifier, a string literal, a number literal, a private field nam

... [truncated 1178 characters] ...

cted a statement but instead found '<<<<<<< HEAD

    this._suggestionIndex = 0'.; Line 316: Expected a statement but instead found '=======
    this._suggestionIndex = 0'.; Line 325: Expected a statement but instead found '>>>>>>> upstream/main
    this._onChange?.('')'.; Line 395: Expected a statement but instead found '<<<<<<< HEAD
    // Normal input handling

======='.; Line 412: Expected a statement but instead found '>>>>>>> upstream/main'.; Line 419: Expected a case but instead found '<<<<<<< HEAD

=======

upstream/main
case 'backspace':
; Line 528: Expected a statement but instead found '<<<<<<< HEAD'.; Line 540: Expected a statement but instead found '======='.; Line 559: Expected a statement but instead found '>>>>>>> upstream/main'.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/widgets/src/input/TextInput.ts (1)

524-579: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the unresolved merge conflict in _renderSelf. The method still contains <<<<<<< HEAD/=======/>>>>>>> upstream/main markers, nested if (this.isFocused) blocks, and duplicate cursor/suggestion rendering. Keep one rendering path and delete the conflict remnants so the file compiles cleanly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/input/TextInput.ts` around lines 524 - 579, The
`_renderSelf` method still has unresolved merge conflict markers and duplicated
focused-rendering logic. Remove the `<<<<<<< HEAD`/`=======`/`>>>>>>>
upstream/main` remnants, collapse the nested `if (this.isFocused)` blocks into
one path, and keep a single consistent implementation for cursor, mode
indicator/counter, and inline suggestions rendering in `TextInput`.
packages/widgets/src/display/Breadcrumbs.ts (1)

41-50: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

_selectedIndex isn't clamped when segments shrink.

setSegments doesn't reset or clamp _selectedIndex. If a caller replaces segments with a shorter array, _selectedIndex can point past the new array's end, so this._segments[this._selectedIndex] in the enter branch (line 65) would be undefined, and _onSelect would fire with an undefined label; also no segment would render as active since originalIndex never matches.

🛡️ Proposed fix
     setSegments(segments: string[]): void {
         if (
             this._segments.length === segments.length &&
             this._segments.every((segment, index) => segment === segments[index])
         ) {
             return;
         }
         this._segments = segments;
+        this._selectedIndex = Math.min(this._selectedIndex, Math.max(0, segments.length - 1));
         this.markDirty();
     }

Also applies to: 62-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/display/Breadcrumbs.ts` around lines 41 - 50,
`Breadcrumbs.setSegments` leaves `_selectedIndex` untouched when the segment
list changes, so a shorter array can make the current selection point past the
end. Update `setSegments` to clamp or reset `_selectedIndex` whenever
`this._segments` is replaced, and keep `enter`/selection rendering in
`Breadcrumbs` safe by ensuring `this._segments[this._selectedIndex]` always
resolves to a valid segment before calling `_onSelect`.
🧹 Nitpick comments (5)
packages/widgets/src/input/TextInput.ts (1)

73-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant duplicate assignment of _suggestions.

this._suggestions = options.suggestions ?? [] is set on line 73 and again on line 76 (under the "Auto-complete data" comment). Remove one.

♻️ Proposed cleanup
         this._onChange = options.onChange;
         this._onSubmit = options.onSubmit;
-        this._suggestions = options.suggestions ?? [];
-
-        // Auto-complete data
         this._suggestions = options.suggestions ?? [];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/input/TextInput.ts` around lines 73 - 76, The TextInput
constructor is assigning `_suggestions` twice with the same `options.suggestions
?? []` value, creating redundant duplicate initialization. Remove the extra
assignment in `TextInput` and keep a single `_suggestions` initialization near
the auto-complete setup so the constructor only sets it once.
packages/widgets/src/display/Breadcrumbs.ts (1)

116-116: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename isLast — it no longer reflects its meaning.

The variable now indicates the selected segment, not the last one; the stale name hurts readability.

♻️ Proposed rename
-            const isLast = originalIndex === this._selectedIndex;
+            const isSelected = originalIndex === this._selectedIndex;
             renderList.push({ 
-                type: isLast ? 'active' : 'normal', 
+                type: isSelected ? 'active' : 'normal', 
                 text: visibleSegments[i] 
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/display/Breadcrumbs.ts` at line 116, The variable name
in Breadcrumbs no longer matches its purpose: the boolean computed from
originalIndex and this._selectedIndex identifies the selected segment, not the
last segment. Rename the local in Breadcrumbs to something like selected/
isSelected and update its usages in the same rendering logic so the name
reflects the actual meaning.
packages/widgets/src/layout/Workspace.ts (2)

36-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

switchWorkspace fails silently for unknown names.

No error, return value, or log indicates whether the switch succeeded, making it easy for callers to assume they're on a different workspace when they're not.

♻️ Suggested tweak
-    switchWorkspace(name: string): void {
+    switchWorkspace(name: string): boolean {
         if (this.layouts.has(name)) {
             this.activeWorkspace = name;
+            return true;
         }
+        return false;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/layout/Workspace.ts` around lines 36 - 40,
switchWorkspace currently does nothing when the requested workspace name is
unknown, so callers cannot tell whether the active workspace changed. Update
Workspace.switchWorkspace to surface success or failure by either returning a
boolean, throwing on unknown names, or logging a clear failure, and make the
behavior explicit when this.layouts does not contain the requested name. Use the
existing switchWorkspace method and activeWorkspace/layouts members to locate
the change.

58-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for save()/restore().

The provided Workspace.test.ts snippet covers only the in-memory CRUD operations; save()/restore() — the code paths most likely to break due to serialization or adapter mismatches — have no test coverage. Given the key-mismatch bug flagged above, tests exercising a mock WorkspaceStorage across saverestore round-trips (including with a switched workspace) would have caught this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/layout/Workspace.ts` around lines 58 - 73, Add test
coverage for the Workspace save/restore flow in Workspace.test.ts by exercising
the Workspace.save and Workspace.restore methods with a mocked WorkspaceStorage.
Create a round-trip test that saves the current layouts, restores them into a
new Workspace instance, and verifies the layouts are preserved, then add a
second case that switches activeWorkspace before restore to catch key mismatches
between active workspace selection and stored data. Use the Workspace and
WorkspaceStorage symbols to locate the affected paths.
packages/widgets/src/layout/Workspace.test.ts (1)

1-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for save()/restore() persistence via WorkspaceStorage.

The suite covers saveLayout/loadLayout, switchWorkspace, getWorkspaceNames, removeWorkspace, and clear, but not the save(storage)/restore(storage) methods that serialize/deserialize layouts through a WorkspaceStorage adapter — the core feature of this cohort ("Persistence via WorkspaceStorage"). A round-trip test with a simple in-memory mock storage would catch serialization regressions (e.g., Map↔JSON conversion issues).

✅ Suggested additional test
+    it('persists and restores layouts via storage', () => {
+        const store = new Map<string, string>();
+        const storage = {
+            save: (key: string, data: string) => store.set(key, data),
+            load: (key: string) => store.get(key),
+        };
+
+        const workspace = new Workspace();
+        workspace.saveLayout('dashboard', { id: 'dashboard', panels: [] });
+        workspace.save(storage);
+
+        const restored = new Workspace();
+        restored.save(storage); // no-op, just to align activeWorkspace key if needed
+        restored.restore(storage);
+
+        expect(restored.loadLayout('dashboard')).toEqual({ id: 'dashboard', panels: [] });
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/layout/Workspace.test.ts` around lines 1 - 85, The
Workspace test suite is missing coverage for the persistence API exposed by
Workspace.save and Workspace.restore. Add a round-trip test using a simple
in-memory WorkspaceStorage mock to verify that layouts are serialized, saved,
restored, and reloaded correctly, and that the current workspace and workspace
names survive the save/restore flow. Use the existing Workspace class methods
and a mocked storage adapter to catch Map-to-JSON or deserialization
regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/widgets/src/index.ts`:
- Around line 131-138: The public barrel in index.ts is missing the
SplitDirection export, while the Workspace-related type exports are already
correct. Add SplitDirection back to the existing export list alongside
SplitPaneOptions in the widgets index so consumers can import it from the
package root, and keep the current Workspace re-exports unchanged.

In `@packages/widgets/src/input/TextInput.ts`:
- Around line 33-48: Resolve all Git merge conflict markers in TextInput by
manually choosing the correct implementation for each duplicated hunk so the
file is valid TypeScript again. Keep a single consistent definition for the
_vimMode, _suggestions, and _suggestionIndex fields in TextInput, and ensure
methods like getSuggestions, insertChar, handleKey, and _renderSelf are present
only once with coherent control flow. Remove every <<<<<<<, =======, and >>>>>>>
marker and verify the resolved behavior does not duplicate _onChange calls or
suggestion-index resets.

In `@packages/widgets/src/layout/Workspace.ts`:
- Around line 65-72: The restore path in Workspace.restore currently feeds
JSON.parse(data) straight into this.layouts without validation or error
handling. Wrap the parse in try/catch, validate that the parsed value is a
Map-compatible iterable of WorkspaceLayout entries before assigning, and fall
back safely if storage is corrupted or from an incompatible schema. Keep the fix
localized to Workspace.restore and the this.layouts assignment so malformed
payloads cannot become active state.
- Around line 58-73: `Workspace.save()` and `Workspace.restore()` currently use
the mutable `activeWorkspace` as the storage key, which breaks round-tripping
when `switchWorkspace` changes the active workspace. Update both methods in
`Workspace` to read and write the serialized `layouts` map under a fixed, stable
storage identifier for the whole workspace collection instead of
`this.activeWorkspace`, so `restore()` can always find the saved data regardless
of the current active workspace.

---

Outside diff comments:
In `@packages/widgets/src/display/Breadcrumbs.ts`:
- Around line 41-50: `Breadcrumbs.setSegments` leaves `_selectedIndex` untouched
when the segment list changes, so a shorter array can make the current selection
point past the end. Update `setSegments` to clamp or reset `_selectedIndex`
whenever `this._segments` is replaced, and keep `enter`/selection rendering in
`Breadcrumbs` safe by ensuring `this._segments[this._selectedIndex]` always
resolves to a valid segment before calling `_onSelect`.

In `@packages/widgets/src/input/TextInput.ts`:
- Around line 524-579: The `_renderSelf` method still has unresolved merge
conflict markers and duplicated focused-rendering logic. Remove the `<<<<<<<
HEAD`/`=======`/`>>>>>>> upstream/main` remnants, collapse the nested `if
(this.isFocused)` blocks into one path, and keep a single consistent
implementation for cursor, mode indicator/counter, and inline suggestions
rendering in `TextInput`.

---

Nitpick comments:
In `@packages/widgets/src/display/Breadcrumbs.ts`:
- Line 116: The variable name in Breadcrumbs no longer matches its purpose: the
boolean computed from originalIndex and this._selectedIndex identifies the
selected segment, not the last segment. Rename the local in Breadcrumbs to
something like selected/ isSelected and update its usages in the same rendering
logic so the name reflects the actual meaning.

In `@packages/widgets/src/input/TextInput.ts`:
- Around line 73-76: The TextInput constructor is assigning `_suggestions` twice
with the same `options.suggestions ?? []` value, creating redundant duplicate
initialization. Remove the extra assignment in `TextInput` and keep a single
`_suggestions` initialization near the auto-complete setup so the constructor
only sets it once.

In `@packages/widgets/src/layout/Workspace.test.ts`:
- Around line 1-85: The Workspace test suite is missing coverage for the
persistence API exposed by Workspace.save and Workspace.restore. Add a
round-trip test using a simple in-memory WorkspaceStorage mock to verify that
layouts are serialized, saved, restored, and reloaded correctly, and that the
current workspace and workspace names survive the save/restore flow. Use the
existing Workspace class methods and a mocked storage adapter to catch
Map-to-JSON or deserialization regressions.

In `@packages/widgets/src/layout/Workspace.ts`:
- Around line 36-40: switchWorkspace currently does nothing when the requested
workspace name is unknown, so callers cannot tell whether the active workspace
changed. Update Workspace.switchWorkspace to surface success or failure by
either returning a boolean, throwing on unknown names, or logging a clear
failure, and make the behavior explicit when this.layouts does not contain the
requested name. Use the existing switchWorkspace method and
activeWorkspace/layouts members to locate the change.
- Around line 58-73: Add test coverage for the Workspace save/restore flow in
Workspace.test.ts by exercising the Workspace.save and Workspace.restore methods
with a mocked WorkspaceStorage. Create a round-trip test that saves the current
layouts, restores them into a new Workspace instance, and verifies the layouts
are preserved, then add a second case that switches activeWorkspace before
restore to catch key mismatches between active workspace selection and stored
data. Use the Workspace and WorkspaceStorage symbols to locate the affected
paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fa4c76ec-d2ce-4c8f-a733-eaebb2ecb2e5

📥 Commits

Reviewing files that changed from the base of the PR and between 5f479e6 and 4c2827a.

📒 Files selected for processing (5)
  • packages/widgets/src/display/Breadcrumbs.ts
  • packages/widgets/src/index.ts
  • packages/widgets/src/input/TextInput.ts
  • packages/widgets/src/layout/Workspace.test.ts
  • packages/widgets/src/layout/Workspace.ts

Comment on lines +131 to +138
export type { SplitPaneOptions } from './layout/SplitPane.js';
export { Workspace } from './layout/Workspace.js';

export type {
WorkspaceLayout,
WorkspaceOptions,
WorkspaceStorage,
} from './layout/Workspace.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for any remaining usages of SplitDirection that would break after removal from the public index
rg -n 'SplitDirection' --type=ts -C2

# Confirm Workspace.ts actually exports WorkspaceLayout, WorkspaceOptions, WorkspaceStorage as types
fd Workspace.ts packages/widgets/src/layout --exec cat -n {}

# Check widgets tsconfig for compile scope (is `include` limited to non-test files?)
fd tsconfig.json packages/widgets --exec cat -n {}

Repository: Karanjot786/TermUI

Length of output: 156


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== index excerpt ==\n'
cat -n packages/widgets/src/index.ts | sed -n '120,150p'

printf '\n== SplitPane symbols ==\n'
rg -n --context 2 'SplitDirection|SplitPaneOptions|export .*SplitPane' packages/widgets/src/layout/SplitPane.ts packages/widgets/src/index.ts

printf '\n== Workspace symbols ==\n'
rg -n --context 2 'WorkspaceLayout|WorkspaceOptions|WorkspaceStorage|export .*Workspace' packages/widgets/src/layout/Workspace.ts packages/widgets/src/index.ts

printf '\n== SplitDirection usages ==\n'
rg -n --context 2 'SplitDirection' packages/widgets/src

Repository: Karanjot786/TermUI

Length of output: 8699


Restore the SplitDirection export from packages/widgets/src/index.ts. WorkspaceLayout, WorkspaceOptions, and WorkspaceStorage are already re-exported correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/index.ts` around lines 131 - 138, The public barrel in
index.ts is missing the SplitDirection export, while the Workspace-related type
exports are already correct. Add SplitDirection back to the existing export list
alongside SplitPaneOptions in the widgets index so consumers can import it from
the package root, and keep the current Workspace re-exports unchanged.

Source: Pipeline failures

Comment on lines +33 to +48
<<<<<<< HEAD

// Auto-complete
private _suggestions: string[] = [];
private _suggestionIndex = 0;

// Vim mode support
private _vimMode: VimMode =
process.env.TERMUI_KEYBINDINGS === 'vim'
? 'normal'
: 'insert';
=======
private _vimMode: VimMode = process.env.TERMUI_KEYBINDINGS === 'vim' ? 'normal' : 'insert';
private _suggestions: string[] = [];
private _suggestionIndex = 0;
>>>>>>> upstream/main

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Blocker: unresolved Git merge conflict markers throughout the file.

The file still contains <<<<<<< HEAD, =======, and >>>>>>> upstream/main markers at multiple locations (lines 33/44/48, 119/122/134, 147/170/172, 193/224/250, 312/325, 395/412, 419/421/422, 528/540/559). This is not valid TypeScript and will fail to compile/lint — Biome reports parse errors on each of these lines.

The conflict must be resolved by hand-picking the correct side for each hunk before this can merge. A few resolutions to watch out for while doing so:

  • The _vimMode/_suggestions/_suggestionIndex field block (33-48) is duplicated across both sides — keep one definition.
  • insertChar (147-172): both sides set _suggestionIndex = 0 and call _onChange; ensure the resolved version does each exactly once.
  • getSuggestions appears on both sides (123-124 vs 197-203) — keep a single definition.
  • handleKey's non-vim block and _renderSelf's cursor block (528-559) are split across the markers; verify the resolved control flow is coherent.

Also applies to: 119-134, 147-172, 193-250, 312-325, 395-412, 419-422, 528-559

🧰 Tools
🪛 Biome (2.5.1)

[error] 33-33: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '<<'.

(parse)


[error] 44-44: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '==='.

(parse)


[error] 48-48: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '>'.

(parse)


[error] 48-48: expected a semicolon to end the class property, but found none

(parse)


[error] 48-48: Expected an identifier, a string literal, a number literal, a private field name, or a computed name but instead found '/'.

(parse)

🪛 GitHub Actions: CI / 0_build-and-test.txt

[error] 33-33: esbuild: Expected identifier but found "<<" at line 33. Build failed.


[error] 33-33: TypeScript (DTS build): Merge conflict marker encountered. (TS1185)


[error] 44-44: TypeScript (DTS build): Merge conflict marker encountered. (TS1185)


[error] 48-48: TypeScript (DTS build): Merge conflict marker encountered. (TS1185)

🪛 GitHub Actions: CI / build-and-test

[error] 33-33: esbuild: Build failed. Expected identifier but found "<<" (merge conflict marker).


[error] 44-44: TypeScript: TS1185 Merge conflict marker encountered.


[error] 48-48: TypeScript: TS1185 Merge conflict marker encountered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/input/TextInput.ts` around lines 33 - 48, Resolve all
Git merge conflict markers in TextInput by manually choosing the correct
implementation for each duplicated hunk so the file is valid TypeScript again.
Keep a single consistent definition for the _vimMode, _suggestions, and
_suggestionIndex fields in TextInput, and ensure methods like getSuggestions,
insertChar, handleKey, and _renderSelf are present only once with coherent
control flow. Remove every <<<<<<<, =======, and >>>>>>> marker and verify the
resolved behavior does not duplicate _onChange calls or suggestion-index resets.

Source: Linters/SAST tools

Comment on lines +58 to +73
save(storage: WorkspaceStorage): void {
storage.save(
this.activeWorkspace,
JSON.stringify([...this.layouts])
);
}

restore(storage: WorkspaceStorage): void {
const data = storage.load(this.activeWorkspace);

if (!data) return;

this.layouts = new Map(
JSON.parse(data)
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

save()/restore() key on the mutable activeWorkspace, breaking round-trips.

save() serializes the entire layouts map but stores it under the key this.activeWorkspace, and restore() looks it up the same way. If the active workspace changes between calls (which is the whole point of switchWorkspace), restore() will look under the new name and silently find nothing (line 68 swallows this), or save() will scatter duplicate copies of the full state across multiple storage keys named after whichever workspace happened to be active. The storage key should be a fixed identifier for "all workspaces", decoupled from activeWorkspace.

🐛 Proposed fix
+const WORKSPACE_STORAGE_KEY = "termui:workspaces";
+
 export class Workspace {
     private layouts = new Map<string, WorkspaceLayout>();
     private activeWorkspace: string;
@@
     save(storage: WorkspaceStorage): void {
         storage.save(
-            this.activeWorkspace,
-            JSON.stringify([...this.layouts])
+            WORKSPACE_STORAGE_KEY,
+            JSON.stringify({
+                activeWorkspace: this.activeWorkspace,
+                layouts: [...this.layouts],
+            })
         );
     }

     restore(storage: WorkspaceStorage): void {
-        const data = storage.load(this.activeWorkspace);
+        const data = storage.load(WORKSPACE_STORAGE_KEY);

         if (!data) return;

-        this.layouts = new Map(
-            JSON.parse(data)
-        );
+        const parsed = JSON.parse(data) as {
+            activeWorkspace: string;
+            layouts: [string, WorkspaceLayout][];
+        };
+        this.layouts = new Map(parsed.layouts);
+        this.activeWorkspace = parsed.activeWorkspace;
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/layout/Workspace.ts` around lines 58 - 73,
`Workspace.save()` and `Workspace.restore()` currently use the mutable
`activeWorkspace` as the storage key, which breaks round-tripping when
`switchWorkspace` changes the active workspace. Update both methods in
`Workspace` to read and write the serialized `layouts` map under a fixed, stable
storage identifier for the whole workspace collection instead of
`this.activeWorkspace`, so `restore()` can always find the saved data regardless
of the current active workspace.

Comment on lines +65 to +72
restore(storage: WorkspaceStorage): void {
const data = storage.load(this.activeWorkspace);

if (!data) return;

this.layouts = new Map(
JSON.parse(data)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unvalidated JSON.parse result flows straight into layouts.

JSON.parse(data) returns any and is passed directly into new Map(...) with no shape validation or try/catch. Corrupted or incompatible storage payloads (e.g. from an older schema version) will either throw uncaught or silently populate layouts with malformed entries typed as WorkspaceLayout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/widgets/src/layout/Workspace.ts` around lines 65 - 72, The restore
path in Workspace.restore currently feeds JSON.parse(data) straight into
this.layouts without validation or error handling. Wrap the parse in try/catch,
validate that the parsed value is a Map-compatible iterable of WorkspaceLayout
entries before assigning, and fall back safely if storage is corrupted or from
an incompatible schema. Keep the fix localized to Workspace.restore and the
this.layouts assignment so malformed payloads cannot become active state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:widgets @termuijs/widgets needs-star PR author has not starred the repo. type:feature +10 pts. New feature. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Breadcrumb Navigation Component

1 participant