Skip to content

Code tab: right-click Open path:N jumps to file at that line#891

Merged
srid merged 22 commits into
masterfrom
open-in-code-tab
May 14, 2026
Merged

Code tab: right-click Open path:N jumps to file at that line#891
srid merged 22 commits into
masterfrom
open-in-code-tab

Conversation

@srid
Copy link
Copy Markdown
Member

@srid srid commented May 14, 2026

The Code tab's right-click menu gains an Open <path>:<line> entry. Reviewing a diff and wanting the full file at the same line was previously two steps — copy path:N, switch to browse mode, paste-navigate. Now it's one click; the menu item dispatches through the same pipeline a terminal-link click does.

This is Phase 0 of #881's redo — small user-visible win on top of a seam Phase 1 (line-anchored comments) will reuse. The structural review behind the redo is in #881's body and replaces #879.

Before / after

Diff view: alpha
           beta            ← right-click line 2
           gamma

Before:    [Copy path]    [Copy notes.txt:2]
After:     [Copy path]    [Copy notes.txt:2]    [Open notes.txt:2]
                                                 └──┐
                                          one click ▼
                                          browse mode, file open, line 2 highlighted

The seam

The terminal-link click pipeline used to require a paired write at every call site:

// Terminal.tsx (before)
rightPanel.openCodeBrowser();   // ① uncollapse + tab + browse-mode
requestCodeOpen({ ref, repoRoot, cwd, targetMode: "browse" });  // ② pending
// Comment: "must fire in the same DOM-event tick or resetKey clears
//  what pendingCodeOpen is about to set."

The ordering hazard sat at the call site as documented discipline, which meant every new producer (right-click Open, future Comment on) inherited the same wall-of-text rationale. Phase 0 collapses both writes into one function:

// Terminal.tsx (after)
openInCodeTab({ ref, repoRoot, cwd, targetMode: "browse" });

openInCodeTab (replacing codeNavigation.ts) encapsulates both writes; useRightPanel.openCodeBrowser() is generalized to openCodeAt(mode: CodeTabView) so a Phase 1 comment authored while reviewing a diff can re-open in that diff mode rather than always browse.

What landed

Layer Change
Producer-facing openInCodeTab(req) — single call replaces the paired-write pattern at every site
Panel state useRightPanel.openCodeAt(mode) — atomic three-field preferences patch parameterized on the target sub-mode; skips the patch when already at target
Menu data CodeContextMenuItem becomes a discriminated union (kind: "copy" vs kind: "action"); handleItem dispatches via ts-pattern.exhaustive()
Selection hook useLineSelection gains onOpen?: Accessor<((ref) => void) | undefined>; when present and a range is selected, an Open path:N action item is emitted
Code tab wiring The diff-view CodeMenuFrame passes onOpen that routes through openInCodeTab. The browse-view path deliberately omits it — the file is already on screen at line precision

The Accessor<…> shape on onOpen mirrors initialRange — buildItems runs at menu-open time, so a host whose prop arrives late in the SolidJS lifecycle still flips the item in.

Test coverage

  • code-tab.feature — new scenario: right-click in diff view, click Open notes.txt:2, assert mode flipped to browse + file selected + line 2 highlighted
  • code-tab.feature — updated multi-file diff scenario to assert the three-item menu (Copy path | Copy file-b.txt:1 | Open file-b.txt:1)
  • file-ref-link.feature — terminal-link clicks now flow through openInCodeTab; all three pre-existing scenarios still pass

43/43 scenarios, 503/503 steps locally.

Try it locally

nix run github:juspay/kolu/open-in-code-tab

Generated by /do on Claude Code (model claude-opus-4-7).

srid added 6 commits May 14, 2026 09:48
…ne N"

Generalizes the terminal-link click pipeline into a single `openInCodeTab`
producer-facing function, then exercises the new seam by adding an
`Open path:N` entry to the Code tab's right-click context menu.

- Renames `codeNavigation.ts` → `openInCodeTab.ts`. The new function
  encapsulates the paired writes (panel-uncollapse + Code-tab activation
  + browse-mode + pending-request signal) that previously sat at every
  call site as documented ordering discipline (`Terminal.tsx:474-489`).
  Callers now make one call instead of two.
- `useRightPanel.openCodeBrowser` → `openCodeAt(mode)`: the atomic
  three-field preferences patch now takes the target sub-mode as a
  parameter so future producers (comment-revisit in phase 1) can route
  back to the mode the artifact was authored in.
- `CodeContextMenuItem` becomes a discriminated union (`kind: "copy"`
  vs `kind: "action"`). Copy items still write to the clipboard; action
  items fire a callback. `CodeContextMenu` dispatches on `kind`.
- `useLineSelection` accepts an `onOpen` accessor option; when set and
  a range is selected, it emits an `Open path:N` action entry that
  routes the ref through the host's open dispatcher.
- The Code tab's diff-view `CodeMenuFrame` passes `onOpen` that calls
  `openInCodeTab({ ref, repoRoot, targetMode: "browse" })`. Reviewing a
  diff and jumping to the full file at the same line is now one click.
  The browse-view `CodeMenuFrame` deliberately omits `onOpen` — the
  file is already on screen at line precision.

First phase of #881's redo. No new types beyond `OpenInCodeTabRequest`
(rename of `CodeOpenRequest`).
…atch.exhaustive

Replaces an `if (item.kind === "copy") … else …` two-arm with
`match(item).with(…).exhaustive()`. The else-arm silently absorbed any
future `CodeContextMenuItem` variant; the exhaustive check makes the
dispatch site a compile-time gate for the next variant (Phase 1's
"Comment on path:N" item).
`onActivate`'s closure used to call `path()` at click time, so if Pierre
remounted to a different file between menu-open and click the action
opened a path that disagreed with the rendered label. Snapshot once at
the top of `buildItems` and reuse the local in every consumer (label,
textToCopy, onActivate capture).
…rget

`openInCodeTab` calls `openCodeAt(req.targetMode)` unconditionally,
which patches three preference fields and round-trips to the server
even when the panel is already in the requested state — e.g. a
terminal-link click on `notes.txt:3` while the Code tab is already
showing that file in browse mode would have re-sent the same patch.
Guard at the function so every caller benefits.
The second sentence ("Same dispatch as a terminal-link click — openInCodeTab
flips the panel to browse mode and surfaces the line range") narrated
what openInCodeTab does, which is already documented at its definition.
Keep only the non-obvious cwd-omission rationale.
…ode jsdoc

"Today every producer passes …; later phases may route a comment-revisit
click back …" was roadmap speculation that both ages poorly and reads as
a justification for the field's existence. The contract is what matters:
producers without an authoring mode pass "browse".
@srid
Copy link
Copy Markdown
Member Author

srid commented May 14, 2026

Hickey/Lowy Analysis

No findings — analysis below.

Hickey rationale

Reviewed git diff origin/master...HEAD across all 9 files at Layers 1–4. Specific checks against the design choices in this diff:

  • Discriminated CodeContextMenuItem — clean. The previous flat textToCopy shape forced every item to wear a clipboard-write costume; {kind:"copy"} | {kind:"action"} is a proper sum type and handleItem dispatches on kind without leakage.
  • onOpen as Accessor<fn | undefined> — earns its keep. buildItems() runs imperatively at menu-open time, not inside a tracking scope, so the thunk is the standard SolidJS "props are getters" pattern for reading the latest prop value at invocation. The indirection is motivated, not ceremonial.
  • Rename + reshape leftovers — none. No dangling codeNavigation imports, no stale openCodeBrowser references, no dead exports. The useRightPanel import was correctly dropped from Terminal.tsx now that the paired writes live inside openInCodeTab.
  • Encoded-invariant smells — none. The paired writes (uncollapse + tab + mode + pending) are now genuinely co-located in openInCodeTab; no parallel state, no sum-types-as-fields, no hidden cross-module ordering discipline.
  • Concept multiplication on LineRef — the call to keep one type was correct. Introducing an Anchor/ResolvedRef for "resolved vs raw" would have manufactured a second name for the same concept and forced converters at every boundary; the diff's surface doesn't justify it.

Lowy rationale

Reviewed five volatility seams; all came back No-op:

  • OpenInCodeTabRequest.targetMode: CodeTabView — correct axis. The previous hardcoded "browse" was activity volatility leaking into the API. Phase 1's comment-revisit will route back to local/branch/browse per authoring mode. Field is narrow, well-typed, and parameterizes the right thing.
  • useRightPanel.openCodeAt(mode) generalization — not a Code-tab implementation leak. CodeTabView is already the public type the request carries; pushing the decision down to openCodeAt keeps the panel API parameterized on the same axis the navigation request varies on.
  • Module split openInCodeTab.ts vs useRightPanel.ts — two distinct volatility axes, cleanly cut: useRightPanel owns declarative right-panel state (the what), openInCodeTab owns navigation-request orchestration including the paired-write ordering invariant (the how).
  • Accessor<fn | undefined> for onOpen — symmetry with initialRange is structural, not coincidental. Both are SolidJS reactive props that may resolve after hook construction; buildItems() is called lazily at menu-open time. A bare callback captured at hook-construction would silently disable the item in race conditions.
  • Phase 1 seam check — the diff lands cleanly. CodeContextMenuItem discriminated union is the right shape for "Comment on path:N"; useLineSelection.onOpen establishes the (ref) => void accessor pattern Phase 1's onComment will sibling. Resisted adding onComment to this PR — one-action-at-a-time is the right call; Phase 1 will reveal whether two parallel props or a generalized extraActions slot fits better.

Polish commits

After the structural review came back clean, an elegance pass (/code-police/simplify) surfaced five smaller refinements that landed as their own commits:

# Pass Finding
1 Rules / prefer-ts-pattern handleItem if/else → match.exhaustive() so Phase 1's third variant is a compile-time gate
2 Elegance Snapshot path() once per buildItems — defends against Pierre remount between menu-open and click
3 Elegance Skip openCodeAt patch when the panel is already at target state — avoided a redundant 3-field preferences round-trip on every terminal click of an already-open file
4 Elegance Trim narrating second sentence from CodeTab onOpen comment
5 Elegance Drop future-tense speculation from targetMode JSDoc

@srid srid mentioned this pull request May 14, 2026
@srid
Copy link
Copy Markdown
Member Author

srid commented May 14, 2026

Evidence

The visible surface this PR adds is a single new context-menu item plus the navigation it triggers. Both are exercised end-to-end by a new e2e scenario (features/code-tab.feature) which polls the same DOM state a screenshot would capture, so the test transcript stands in for screenshots here.

What the menu looks like in diff view

┌── right-click on the diff line ─┐
│ Copy path                       │
│ Copy notes.txt:2                │
│ Open notes.txt:2          ← new │
└─────────────────────────────────┘

Asserted in code-tab.feature regression for #809/#514 (multi-file diff):

And I right-click the diff view
Then the context menu items should be "Copy path | Copy file-b.txt:1 | Open file-b.txt:1"

What the click does

New scenario in code-tab.feature:

Scenario: Right-click "Open path:N" in diff view jumps to browse at that line
  When I run "git init /tmp/kolu-open-from-diff && cd /tmp/kolu-open-from-diff"
  And I run "git commit --allow-empty -m init"
  And I run "printf 'first\nsecond\nthird\n' > notes.txt"
  And I click the Code tab
  Then the Code tab should list a changed file "notes.txt"
  When I click the changed file "notes.txt" in the Code tab
  Then the diff view should contain "second"
  When I click the line number 2 in the diff view
  And I right-click the diff view
  And I click the context menu item "Open notes.txt:2"
  Then the Code tab mode should be "browse"
  And the selected file should show content "second"
  And line 2 should be selected in the file content

the selected file should show content "second" and line 2 should be selected in the file content use Playwright waitForFunction polling — the assertions only pass when the panel has switched to browse mode, the file is open, and Pierre's gutter has marked line 2 with data-selected-line. Same observable surface a screenshot would show.

Regression coverage retained

features/file-ref-link.feature (three scenarios) covers the terminal-link click path, which now routes through the same openInCodeTab function the new menu item uses. All three scenarios pass:

  • Clicking a file-ref opens the file in browse mode
  • Clicking a line-range file-ref opens the file
  • Re-clicking the same file-ref after closing the panel re-selects the line

Local: 43/43 scenarios, 503/503 steps. CI: 300/300 on both x86_64-linux and aarch64-darwin.

@srid
Copy link
Copy Markdown
Member Author

srid commented May 14, 2026

/do results

Step Status Duration Verification
sync 0s git fetch ok; forge=github; noGit=false
research 2m 11s File inventory, paired-write pattern at Terminal.tsx:468-491, CodeContextMenuItem shape, e2e patterns confirmed
branch 8s On feature branch open-in-code-tab rooted at origin/master
implement 5m 26s Renamed codeNavigation.ts→openInCodeTab.ts; OpenInCodeTabRequest carries targetMode; useRightPanel.openCodeBrowser→openCodeAt(mode); CodeContextMenuItem discriminated; useLineSelection emits Open action via accessor; e2e scenario added
check 5m 37s just check exit 0; pnpm typecheck Done for all 19 packages
docs 45s README's clickable-file-refs bullet still accurate; no architecture refs to renamed module
fmt 14s biome reformatted 1 file (line-break); nixpkgs-fmt 0/5
commit 24s Commit 3d887fdf; 9 files / +178 −91; rename tracked by git
hickey+lowy 3m 50s Hickey: 0 findings. Lowy: 0 findings (5 questions all No-op)
police 8m 40s Rules 1 violation (prefer-ts-pattern), Elegance 5 findings → 5 separate commits (ce1546a4, fc2d1c9b, e267f90a, ee147275, e50f4ac1)
test 1m 8s 43/43 scenarios, 503/503 steps
create-pr 1m 8s Draft PR #891 + Hickey/Lowy analysis comment
ci 11m 56s All 14 contexts success on HEAD; 3 flakes on first run, all passed on ci::e2e retry (reported on #320)
evidence 1m 11s Textual Evidence comment quoting the new e2e scenario's polling assertions
done 4s All 15 steps recorded
Total 43m 12s

Slowest step: ci (11m 56s)

Optimization suggestions

  • CI dominated the run (28%) because of a first-pass flake retry. Two known flakes (OpenCode indicator on x86_64-linux, session-restore click race on aarch64-darwin) cost ~5m of retry time. Both are unrelated to this diff and now logged on Flaky tests log #320; fixing them would unlock a clean ~7m CI run.
  • Police took 8m 40s mostly from the /simplify three-agent fan-out plus five separate fix/format/commit/push cycles. The granularity is by design (one commit per finding for review clarity) but it does compound: each commit pays the cost of just fmt re-running on a clean tree. Worth considering a single-shot lint-after-all-fixes pass in /simplify itself.
  • Check at 5m 37s is dominated by pnpm install round-tripping through the nix devshell on each invocation. Same install runs again in fmt and police's batched fmt — cache invalidation might be too eager.
  • Resume entry points: re-running this with /do --from ci-only would skip everything before CI and land in ~12m if the flakes hold.

Workflow completed at 2026-05-14T14:16:50Z.

srid added 5 commits May 14, 2026 11:00
… just verbal

"Copy path:N" and "Open path:N" differ only by the first word, which is
easy to mis-read when scanning the menu in a hurry. Add a leading glyph
to every CodeContextMenuItem — a clipboard glyph for `kind: "copy"` and
an external-link arrow for `kind: "action"` — so the verb is readable
without parsing the label. CopyIcon and OpenIcon land in the existing
Icons.tsx registry per the icons-in-registry rule; useLineSelection
populates each item's `icon` at build time.
…ation reveals nested files

Pierre's FileTree was constructed with `initialExpandedPaths` from
`props.expandPaths` only; an externally-driven `selectedPath` change
(e.g. a terminal `path:line` click resolving onto `src/foo/bar.ts`)
left the parent directories collapsed and the row off-screen until
the user opened them by hand.

Merge the ancestors of `props.selectedPath` into the
`initialExpandedPaths` set passed to `resetPaths`. Pierre's vanilla
`FileTree` class doesn't expose `selectOnlyPath` post-mount, so the
tree-row `data-item-selected` highlight is still seeded only at
construction — that would need an upstream Pierre API. The
content-level highlight (line range selected in the file viewer)
already verifies user-meaningful selection.

E2e: the existing "Open path:N" scenario now uses `docs/notes.txt`
and asserts `the file browser should show a file "docs/notes.txt"`
(which fails when the parent directory is collapsed because
Playwright's `state: "visible"` rejects collapsed-ancestor descendants).
…ntry point

Right-click on the viewer USED to require a prior left-click to set
the line range — without that, the menu opened with a lone "Copy path"
item, which was both noise (the path is also in every other item) and
a UX trap (users mis-read which item they had).

`CodeMenuFrame`'s contextmenu handler now walks `event.composedPath()`
(piercing Pierre's shadow DOM) for the gutter element carrying
`data-column-number` and sets the selection range from there. Right-
clicks off any line clear the range; `useLineSelection.buildItems`
returns `[]` when no range is set, so `CodeContextMenu.open()` short-
circuits without preventing the browser default. Net: right-click on
a line shows the 3-item menu; right-click on whitespace shows nothing.

E2e: new step `I right-click line N in the diff view` /
`... in the file content` (clicks the gutter at `data-column-number=N`
with the right mouse button). Replaces the old `click line N + right-
click viewer root` idiom in three existing scenarios — they now lead
with a single gesture per the new UX.
…aths at construction

The earlier ancestor-expansion fix only ran in the deferred reset
effect — which doesn't fire on the initial mount. When the Code tab
flips from diff to browse mode, `<FileTree>` remounts FRESH (the
`<Switch>/<Match>` predicate flips with the data source), and the
constructor's `initialExpandedPaths` previously only included
`props.expandPaths`. A `setSelectedPath("docs/notes.txt")` carried in
through `initialSelectedPaths`, but `docs/` stayed collapsed and the
row was offscreen.

Compute selected-path ancestors at construction time too, alongside
the existing deferred-effect path. Both paths now use the same
`ancestorsOf` helper.
…Key effect sees the pending request

`openInCodeTab(req)` issues two synchronous writes — a preferences
patch (`openCodeAt`) and `setPending(req)`. The preferences cell uses
`authority: "local"` (wire.ts:56), so `applyLocal` ticks `view()`
synchronously. Without an explicit batch, SolidJS fired CodeTab's
`resetKey` effect immediately on the first write, BEFORE `setPending`
ran. The guard

    if (req && req.repoRoot === repoPath() && req.targetMode === view()
        && handled()?.request !== req) return;

saw `pendingOpen() === null` (setPending hadn't fired yet), fell
through to `setSelectedPath(null)`, and cleared the path the user had
just navigated to. Symptom: in the test scenario, the file row in the
tree showed `aria-selected="false"` after a `Open path:N` jump,
because the rebuilt browse-mode tree had `selectedPath = null` at
mount time.

The old paired-write at `Terminal.tsx`'s call site escaped this by
sitting inside a JSX click handler — Solid's event delegation
auto-batches writes within a single user event. Once the writes moved
into a standalone function called from a non-event context (an item
callback two layers deep), the implicit batch was lost.

Wrap with `batch()` so both writes commit before any dependent effect
fires. The `resetKey` effect now sees `pendingOpen() = req` on the
tick `view()` changes, the guard passes, and selectedPath survives the
mode flip.

E2e: the "Open path:N" scenario now uses `docs/notes.txt` (nested), and
asserts `the file "docs/notes.txt" should be selected in the file
browser` via `aria-selected="true"`. The assertion polls until visible,
which requires both ancestor expansion AND the selection flag.
srid and others added 11 commits May 14, 2026 14:09
… upstream

Pierre's `scrollFocusedRowIntoView` is gated on `shouldOwnDomFocus`,
which is set only by user-initiated handlers — programmatic selection
via `initialSelectedPaths` / `getItem(path)?.select()` doesn't trigger
it. For small trees the row sits in the visible window; for large
worktrees the selected row stays virtualized off-screen until the user
scrolls. Comment near `onMount` documents the limitation; upstream
request is pierrecomputer/pierre#676.
… into one helper

The two helpers had identical bodies modulo the mouse button — same
selector, same bounding-box poll, same waitForFrame. Drift hazard.

Extract `interactWithGutterLine(world, root, line, button)` parameterized
on `"left" | "right"`. Three step registrations now call through it with
the appropriate button literal. ~25 lines of near-duplicate body removed.
…face effect-ordering coupling

The resetKey effect's guard checked four conditions inline. Naming
the predicate `isPendingOpenAboutToLand()` gives the cross-effect
temporal coupling — resetKey runs BEFORE pendingOpen by registration
order, both effects observe state populated by `openInCodeTab`'s
`batch()` — a discoverable surface. A future editor changing one
effect in isolation now reads a single named function rather than
re-deriving the guard from inline ands.

No behavior change.
Module-level signal is fine while right-panel state is a singleton.
Comment makes the assumption explicit so a future split-panel / multi-
window change doesn't silently race two CodeTab instances on each
other's pending requests.
…cross mount and effect

`onMount` reads `props.selectedPath` as a snapshot at construction
time; the deferred resetPaths effect reads it reactively. The split is
forced by Pierre — there's no API to re-feed `initialExpandedPaths`
after the constructor. The two sites encapsulate two lifecycle moments.

Today's call site initializes selectedPath to null and updates it
post-stream-settle, so the snapshot is always null at mount; a future
caller passing a non-null selectedPath on first render would rely on
the snapshot, NOT the deferred effect (which skips its initial run).
…defs

The two `I right-click the file content` / `I right-click the diff view`
step handlers (and the shared `rightClickViewRoot` helper) had no
remaining feature-file callers after the new right-click-on-line idiom
landed. Dead surface that invites future scenarios to re-introduce the
two-step "click line + right-click viewer" pattern.
…id-pierre

`fileSearch.ts` already had `ancestorDirectoryPaths` for its search-
expansion needs; the Phase 0 work introduced `ancestorsOf` in
`solid-pierre/FileTree.tsx` for the same shape. Both mirrored Pierre's
internal `getAncestorDirectoryPaths` and both fed Pierre's
`initialExpandedPaths`. The natural home is the Pierre wrapper package
(`@kolu/solid-pierre`) — kolu-client already depends on it, and the
helper conceptually belongs next to the FileTree wrapper that needs it.

Take the more capable of the two implementations (the fileSearch.ts
one tolerates trailing slashes), rename `ancestorsOf` → it, export from
`@kolu/solid-pierre`, and consume from `fileSearch.ts`.
…eTab jsdoc

"The Terminal call site used to escape this by being inside a JSX
click handler … but once the writes moved into a standalone function
the implicit batch was lost." Is change-narration. Future readers need
the invariant (why batch is required), not the refactor history.
…Mount comment

The "Today's only call site (CodeTab) initializes selectedPath to null
and updates it via the pendingOpen effect AFTER the FileTree's paths
stream settles, so this snapshot is always null at mount …" paragraph
named a specific caller (will rot when others wire up) and described
hypothetical futures. Keep the invariant: snapshot here vs reactive
in the deferred effect, because Pierre doesn't expose a re-feed hook.
…ne comments

Two blocks were merged into one and the surviving comment carried a
"One shared interaction for any mouse button on a gutter line — only
the button differs" preamble that narrated the refactor.

The right-click-line step comment had "Replaces the old two-step click-
line-then-right-click idiom" — change-narration that won't age.

Keep the load-bearing rationales: why the gutter selector is correct,
why pointerdown/pointerup go through Playwright's mouse API, why the
bounding box needs polling. Drop the narration.
@srid srid marked this pull request as ready for review May 14, 2026 18:46
@srid srid merged commit 11507bb into master May 14, 2026
16 checks passed
@srid srid deleted the open-in-code-tab branch May 14, 2026 18:56
srid added a commit that referenced this pull request May 14, 2026
**The Code tab now resolves a bare filename like `Foo.hs:42` to the
unique repo file ending in that basename.** Compiler output frequently
emits source locations without the full path prefix; previously those
clicks fell through to a "File reference not found" toast even when
exactly one file in the repo could match. Closes #898.

Resolution still tries the path-based candidates first (repo-relative,
cwd-relative, absolute-stripped) — the basename scan only fires when all
of them miss, and only commits to a result when there's exactly one
match. _Ambiguous basenames keep returning `null`; opening the wrong
file is worse than the toast._

```
repo:                       resolveLineRefPath("Foo.hs"):
  src/lib/Foo.hs              1. cwd-rel  → miss
  src/lib/Bar.hs              2. repo-rel → miss
  ...                         3. basename → src/lib/Foo.hs  ✓ (unique)
```

The fallback covers terminal-link clicks and right-click _Open path:N_
alike — both producers route through the single `openInCodeTab` resolver
added in #891.

### Coverage

- **Unit** — `lineRef.test.ts`: unique basename, ambiguous basename →
null, slash-with-bad-prefix falls back, exact path candidate still
preferred over basename
- **E2E** — `file-ref-link.feature`: terminal emits `notes.txt:2` while
the file lives at `src/lib/notes.txt`; click opens the file in browse
mode at line 2

### Try it locally

\`\`\`sh
nix run github:juspay/kolu/fix-898-basename-fileref
\`\`\`

_Generated by [\`/do\`](https://github.com/srid/agency) on Claude Code
(model \`claude-opus-4-7\`)._
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