Skip to content

chore: reconcile next into master - #86

Merged
damusix merged 6 commits into
masterfrom
chore/reconcile-next-into-master
Sep 8, 2026
Merged

chore: reconcile next into master#86
damusix merged 6 commits into
masterfrom
chore/reconcile-next-into-master

Conversation

@damusix

@damusix damusix commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Reconciles next into master. Merge commit only — no new source changes.

Why they conflicted

next and master each carried the ink7 work as a separate commit with a different SHA (06ad1d22 vs bab7dfcd), from being merged into the two branches independently. Their patch-ids are identical, but that commit added several files, so at the merge base those files do not exist and git reports add/add on every one:

  • src/tui/mouse.tsx
  • src/tui/components/terminal/RowViewOverlay.tsx
  • src/tui/components/terminal/index.ts
  • src/tui/screens/db/explore/ExploreDetailScreen.tsx
  • src/tui/screens/db/explore/FullTextOverlay.tsx

Each was resolved to next's side, after diffing the pair per file: next's version is master's plus the wheel-scroll additions from 37abd22c, with nothing master-only in any of them. git diff origin/next -- src tests on the result is empty.

The part that wasn't mechanical

master's Version Packages commit released 1.2.0 and consumed the changesets. next never saw that release, so it still carried the files — and merging brought them back. Two of the three had already shipped:

Changeset Shipped in Action
tui-ink7-and-row-inspection.md packages/cli 1.2.0 removed
connection-timeouts.md packages/sdk 1.2.0 (text is verbatim in the changelog) removed
inspect-scroll-and-masked-secrets.md not released — only ever on next kept

Left alone, the next release would have bumped 1.2.0 a second time and reprinted release notes for changes already published.

Package versions stay at master's 1.2.0. No CHANGELOG entries were altered.

Verification

  • typecheck clean, lint clean
  • CLI group: 946 pass / 2 fail — the documented pre-existing baseline (two column-fit tests that assume a 100-column terminal; they fail identically on clean master)
  • git diff origin/next -- src tests empty, so no source from next was dropped in the resolution

Ordering

This does not include PR #85 (history screens), which targets next and is now conflict-free there. Either order works: merge this first and #85 flows to master on the next reconcile, or merge #85 into next first and rerun this merge.

Worth deciding separately: the duplicate-SHA divergence will keep producing this conflict every cycle as long as the same PR is merged into both next and master independently.

The Ink 7 upgrade is what made the rest possible: useWindowSize replaced
the hardcoded row counts, alternateScreen let the shell own the window,
and measureElement gained the coordinates a click needs.

TextInput is ours now because @inkjs/ui does not publish the hooks to
rebuild its handler, and a mouse report reaches every useInput handler —
clicking mid-edit typed the escape sequence into the field.

Cancelling is honest per dialect: postgres and mysql ask the server to
stop, mssql and sqlite can only stop waiting, and the wording differs.
Ink has no scroll offset, so a viewport can only draw a slice of a flat
list and a tree cannot be sliced. Inspect built its context as a nested
tree, so everything past the fold was unreachable and the screen's own
footer was what got pushed off to make room. All four views — summary,
expanded shapes, rendered SQL, render error — are now one element per
visual line behind `ScrollPane`.

One row must stay one row or the window arithmetic is wrong by the
difference. `wrap="truncate"` bounds a cell's width but not its height,
so `oneLine` flattens line breaks where text enters a one-row cell:
nothing here controls what it displays, and a secret can be a PEM key, an
environment variable can hold anything, and a helper's error message can
be a multi-line diagnostic.

The screen opens one focus scope with `skip` rather than one per phase.
React runs a child's effects before its parent's, so a screen-level push
lands above its own `SearchableList` and takes the keys the picker was
mounted to receive.

`$.secrets` reported a key count, which cannot answer what the screen is
opened to ask: a stale password and a fresh one are both `Object (7
keys)`. It shows a partial reveal instead, narrowing as the value
shortens, with the length beside it as a number rather than encoded in
the mask width. Banding counts code points, so a non-BMP character is
never sliced through a surrogate pair. `$.env` gets the same treatment —
it is the whole of `process.env` and nothing here can tell which keys are
credentials.

The mouse wheel now scrolls every viewport. It reached only `SelectList`
and `ResultTable`, the two `useRowMouse` callers, so the explore detail
view, the full-text overlay and the row viewer ignored it. That is worse
than unhandled: the TUI runs in the alternate screen, which has no
scrollback, and mouse tracking takes the notches a terminal would
otherwise translate into arrow keys — turning the mouse on is what
removed the only wheel behaviour those panes had. `useWheelScroll` is the
wheel-only half of `useRowMouse`, which needs row refs a viewport has
none of.

Also fixes an error branch that could never render: a helper that failed
to load set the error phase with a file selected, which required
`!selectedFile`, so the likeliest failure showed as "Unknown phase".

Claude-Session: https://claude.ai/code/session_01JGZ8ocdNed7TFFEbccQsW2
next and master both carried the ink7 work as separate commits with different
SHAs, so the files that commit *added* — mouse.tsx, RowViewOverlay, and the two
explore overlays — did not exist at the merge base and came back as add/add
conflicts on every attempt. Each was resolved to next's side after checking the
pair: next's version is master's plus the wheel-scroll additions, with nothing
master-only in any of them.

Drop tui-ink7-and-row-inspection and connection-timeouts from .changeset/.
master's Version Packages commit already consumed both into 1.2.0 — the ink7
notes are in packages/cli/CHANGELOG.md and the connect-timeout note is verbatim
in packages/sdk/CHANGELOG.md — while next never saw that release and still
carried the files. Merging them back would bump 1.2.0 a second time for changes
that already shipped. inspect-scroll-and-masked-secrets stays: it has only ever
been on next and is still unreleased.

Package versions stay at master's 1.2.0.
Both history screens drew a fixed slice while their cursor ranged over every
record, so past the window the selection moved somewhere invisible and the
detail box described a row that was not on screen. Move them onto SelectList,
which owns the cursor and the window together, and give it a renderItem prop so
they keep the per-status colour that made them hand-roll a list to begin with.

A failed file's error was drawn one row per newline with no bound, which took
the list and the footer down with it; the history screen cut the same message
at 80 characters instead. Add TextOverlay for the whole message, wrapped and
scrollable, behind one key.

Record the remaining ~40 unbounded-render sites as a follow-up rather than
waiting to be told about them one at a time.
@damusix

damusix commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

CI is red on this PR — and identically red on master before it

The CI workflow's build job fails here with 9 tests. The same job fails on master's current HEAD (634597f8) with the same 9 tests, by name, and has been failing since bab7dfcd landed on 2026-08-22. The Release workflow is the one that has been green, which is why releases kept shipping.

pass fail
master @ 634597f8 (run 32555067359) 919 9
this PR (run 34183934896) 939 9

Same failures, +20 passing. This merge introduces none of them.

What the 9 actually are

1. mock.module contamination, surfacing only on Linux (2 tests)cli: app > should tell the help screen reader how to turn the mouse back on and provider hierarchy > should leave the mouse off when settings.yml writes ui.mouse: false. The test writes ui.mouse: false and asserts the help footer says Mouse off.; CI renders Mouse on.

This is the failure mode the root CLAUDE.md already documents for cli-logger-settings.test.ts: two init-screen tests replace the SettingsManager class, Bun's mock registry is process-global and never restores, so whichever file loads first decides what every later file sees. Load order is root-files-first on macOS and the reverse on Linux — so it passes locally and fails only on CI. These two need the same treatment cli-logger-settings.test.ts got: their own serial group.

2. explore peek navigation (5 tests) — three assertion failures plus two 5s timeouts. Passes in isolation locally, so contamination and/or the fixed-sleep flake class .claude/rules/tui-development.md calls out.

3. Terminal-width assumption (2 tests)ResultTable column fit and sql result browsing chop both assume a 100-column terminal. Fails on CI and locally alike.

None of these are terminal-only cosmetics — #1 is a genuine "settings.yml is not honored" signal under a poisoned module registry, which is worth fixing rather than re-grouping around, if the mock can be dropped.

Happy to take these on in a separate PR; they predate this branch and I did not want to widen a merge-reconciliation commit into a test-infrastructure change.

@damusix

damusix commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator Author

Correction: I named the wrong file above, and now have the proven cause

My earlier comment blamed the init-screen.test.tsx / SettingsManager pattern by analogy to the documented cli-logger-settings.test.ts case. That was a guess and it is wrong. Reproduced on Linux in a container running CI's pinned Bun 1.3.11:

run result
app.test.tsx alone, Linux 22 pass, 0 fail
app-context.test.tsx then app.test.tsx, Linux 40 pass, 2 fail — the exact two CI failures
same pair, macOS (Bun 1.3.11 and 1.4.0) 42 pass, 0 fail
init-screen.test.tsx then app.test.tsx, either platform mouse tests pass — not the culprit

The leak. tests/cli/app-context.test.tsx:50 does mock.module('../../src/core/index.js', ...) and replaces getSettingsManager with one returning a fixed fake:

const createMockSettingsManager = () => ({ settings: { version: '0.1.0' }, ... });

src/tui/app-context.tsx:452 reads getSettingsManager(projectRoot) from that same barrel. So once the mock is registered, the app reads that fake object instead of the settings.yml the test wrote. The fake has no ui section, and isMouseEnabled reads a missing ui as on — which is precisely the Mouse on. CI reports where ui.mouse: false was written.

Its afterAll re-registers the real barrel, which does nothing. I probed that directly on Bun 1.4.0: re-registering after a mock.module still resolves to the mock (AFTER_REREGISTER=mocked), so CLAUDE.md's "never restores" note still holds on current Bun.

Proof it is this object and not something else. Adding ui: { mouse: false } to that fake — touching only app-context.test.tsx — inverts which of app.test.tsx's assertions fail: the two Mouse off. tests start passing and the three Mouse on. tests start failing (2 fail → 3 fail). The app is unambiguously reading the mock's settings object.

Why Linux only. The same pair passes on macOS under both Bun versions, so this is a platform difference in Bun's mock/module resolution, not a version difference. app-context.test.tsx sorts immediately before app.test.tsx, so it is the first file downstream of the mock.

Why it looked sudden. It is not. bab7dfcd (ink7, 2026-08-22) added the mouse assertions to app.test.tsx; the contamination was already there and had nothing to break until then. CI's build job has been red ever since. It went unnoticed because the Release workflow is green — that is what gates publishing — and because CI only triggers on main/master while recent work has been PR'd to next.

Fix options, cheapest first:

  1. Have app-context.test.tsx mock src/core/settings/index.js / state/index.js directly rather than the src/core/index.js barrel the app itself imports. Narrowest blast radius.
  2. Split app-context.test.tsx into its own serial CI group, the way cli-logger-settings.test.ts already is. Treats the symptom.
  3. Drop the barrel mock in favour of a real SettingsManager pointed at a temp dir.

The remaining 7 failures are unrelated: 5 explore peek navigation (flaky — 2 of them reproduce on macOS under Bun 1.3.11) and 2 column-fit tests that assume a 100-column terminal and fail on macOS too.

Nine tests have failed on CI since bab7dfc. Nobody saw it: the Release
workflow is what gates publishing and it stays green, and CI only triggers on
master while recent work went to next. Three unrelated causes.

app-context.test.tsx replaced the whole src/core/index.js barrel, so the mock
both faked getSettingsManager and deleted the other ~40 exports — for every
file loaded after it, since bun's registry is process-global and re-registering
does not restore (measured on 1.3.11 and 1.4.0). app.test.tsx is next in the
suite and writes a real settings.yml to assert ui.mouse: false; it read the
fake, which has no ui section, and isMouseEnabled treats that as on. Spread the
real module and stop faking settings. Reproduces on Linux only, which is why it
passed locally for everyone.

terminal.test.tsx and sql-row-view.test.tsx located a row with
startsWith('─'). CI sets FORCE_COLOR=true, so every line begins with an SGR
escape, findIndex returned -1, and the assertion indexed past the array. Both
have failed since the day they were written. Read the layout with the escapes
stripped.

explore-row-view.test.tsx wrote arrow keys as soon as the query resolved, but
ResultTable registers useInput in an effect a tick later, so the keystroke was
dropped and the test asserted row two against row one. Settle the grid first,
the way sql-row-view already does.
@damusix
damusix merged commit a4721aa into master Sep 8, 2026
4 checks passed
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