Commit Graph
712 Commits
Author SHA1 Message Date
l0ng-ai 2a0b910cd4 fix(themes): reuse the key that was already written for this failure
f8e2a59 added `OpenInFileManagerFailed` in all three locales for exactly
this case and then only wired its sibling, so the key sat dead — clippy
was reporting it as never constructed. The follow-up fix invented a second
key rather than using it.

Now the notification reads "Could not open /…/themes", naming the path
instead of a category, and `ThemeFolderFailed` is gone. Clippy's
never-constructed list is back to what it is on main.
2026-08-08 06:55:36 +08:00
l0ng-ai b608fcaae9 fix(themes): say so when the themes folder cannot be opened
`open_themes_folder` created the directory with `let _ =` and then handed
the path to the system file manager regardless. On the one click that can
actually fail — the first one on a fresh install, where the folder does
not exist yet — a read-only or permission-denied config directory left the
button doing nothing at all, with no message.

It now reports the failure through the same `notify_err` path as the rest
of the theme editor, and stops instead of opening a path that is not there.

The `themes_dir() == None` branch stays a log line: without a config
directory there is no theme editor to have clicked from.
2026-08-08 06:51:56 +08:00
l0ng-ai 698db815e2 Merge branch 'fix/completion-after-pipe' into integration/polish 2026-08-08 06:49:24 +08:00
l0ng-ai 10abd37134 fix(completion): complete a command name after a pipe, not a filename
`complete()` decided "is this a command position?" with
`chars[..word_start].all(is_whitespace)` — true only at the very start of
the line. Two lines below, `current_command()` already knew better: it
splits the prefix at the last `| & ; \n (` and looks only at the segment
holding the cursor.

So `ls | gre<Tab>` asked for files named `gre*` in the cwd instead of
offering `grep`, and the same for `a && b`, `a; b`. Every shell treats the
first word of each pipeline segment as a command.

`remote_path_request()` carried the identical check, so over SSH the same
keystroke also spent a round trip listing the remote cwd for a word that
was never a path.

Both now go through one `at_command_position()` helper, and the segment
split that was copy-pasted in two places is a `segment_start()` helper.

`ls |gre` — no space — is unchanged: `shell_word_start` only breaks words
on whitespace, so the word is `|gre`. Teaching it to break on
metacharacters has to respect quoting first, or `grep "a|b"` starts
completing `b"`.
2026-08-08 06:49:20 +08:00
l0ng-ai 3e03a1cbb2 refactor(terminal): drop the duration PaneBusy carried and never showed
The close question names what is running, not how long it has been —
carrying the elapsed seconds through the type invited someone to believe
it reached the screen. Command is now a plain name.
2026-08-08 06:33:03 +08:00
l0ng-ai 846ce1defc fix(terminal): invert the half of a wide character that holds the glyph
A TUI can park the cursor on the trailing spacer of a wide character.
Inverting that cell fills the right half with the caret while the glyph,
which lives on the lead cell, keeps its own colour across both halves —
an opaque block over half a character.

The reverse-video pass now steps back to the cell that owns the glyph,
which also lets paint_backgrounds absorb the spacer into its run and
cover both columns. A spacer in column 0 has no lead to step back to and
is left alone rather than wrapping to the previous row.

Found reviewing my own diff; the translucent block this replaced had the
same half-coverage but stayed readable through it.
2026-08-08 06:30:34 +08:00
l0ng-ai befb689a1a fix(editor): keep Discard away from the button Return presses
The unsaved-changes sheet listed [Save, Discard, Cancel]. The platform
draws the first entry as the default and lays the rest out beside it, so
that rendered as "Cancel | Discard | Save" — with Discard directly
adjacent to the key Return lands on. Apple puts Cancel between them for
exactly this reason.

Reordered to [Save, Cancel, Discard], which renders "Discard | Cancel |
Save", and moved the discard arm to index 2 to match.

Tested: the index mapping reads 0 = Save, 1 = Cancel (falls through to
no-op), 2 = Discard. The right-to-left rendering was confirmed on the
two-button prompts, where array index 0 is drawn rightmost and takes
Return; the three-button sheet itself was not driven on screen.
2026-08-08 06:26:05 +08:00
l0ng-ai 5b65267d7f Merge branch 'fix/tree-search-cap' into integration/polish
# Conflicts:
#	src/ui/i18n/mod.rs
2026-08-08 06:03:19 +08:00
l0ng-ai 2ec01b0264 Merge branch 'fix/search-match-cap' into integration/polish 2026-08-08 06:01:43 +08:00
l0ng-ai 5e70a125ea Merge branch 'fix/silent-theme-writes' into integration/polish
# Conflicts:
#	src/ui/i18n/en.rs
#	src/ui/i18n/ja.rs
#	src/ui/i18n/mod.rs
#	src/ui/i18n/zh.rs
2026-08-08 06:01:43 +08:00
l0ng-ai ecaab93c8f fix(file-tree): say when the search list is only the first 200 matches
The tree search stops at SEARCH_LIMIT and returned the prefix with no
mark, so a query with more matches than that looked like it had found
all of them — and the ones you were looking for were simply not there.

A trailing row now reads "First 200 matches", using the same placeholder
rows that already explain an empty or unreadable directory.

Verified against a 260-file directory: the row appears; on this repo,
where no query reaches the ceiling, it does not.
2026-08-08 06:01:19 +08:00
l0ng-ai 4fc2dfd541 fix(search): mark a match count that hit the ceiling
The scrollback scan stops at 10,000 matches. The count rendered that as
"1/10000", which reads as the whole truth — a search for a common
letter looked like it had found every occurrence and settled on a round
number.

It now reads "1/10000+" once the ceiling is reached, the same way every
search that caps says so.
2026-08-08 05:50:50 +08:00
l0ng-ai f8e2a59456 fix(themes): stop the theme editor from failing without saying so
"Duplicate to edit" logged its failure and did nothing else — a button
that does nothing is indistinguishable from having clicked the wrong
thing. Worse, every colour edit runs through mutate_active_theme, which
returned early on a write failure: the picker moved, the theme did not,
and nothing said why.

Both now report, through the same notify_err the rest of the app uses.
On a themes folder the user cannot write, that reads "Could not
duplicate the theme: You do not have permission." rather than
"Permission denied (os error 13)", or nothing at all.
2026-08-08 05:46:18 +08:00
l0ng-ai b2c2ecbc45 Repair the merged plural table and test module 2026-08-08 05:38:15 +08:00
l0ng-ai 2ef162da36 Merge branch 'fix/restart-server-failure' into integration/polish
# Conflicts:
#	src/ui/app.rs
#	src/ui/i18n/en.rs
#	src/ui/i18n/ja.rs
#	src/ui/i18n/zh.rs
2026-08-08 05:34:25 +08:00
l0ng-ai f49281d53c Merge branch 'fix/theme-load-errors' into integration/polish
# Conflicts:
#	src/ui/i18n/mod.rs
2026-08-08 05:34:24 +08:00
l0ng-ai 08c47e37fb Merge branch 'fix/modal-scrims' into integration/polish 2026-08-08 05:33:03 +08:00
l0ng-ai 14156f51a4 fix(app): say when a server restart failed instead of showing an empty home page
Restarting the background server is something the user explicitly asks
for, and a failure logged the reason and left them on the home page with
nothing on it — which looks exactly like the restart working and taking
every tab with it.

The reason now lands on the home page, in the same slot a failed first
terminal uses.
2026-08-08 05:32:37 +08:00
l0ng-ai 6a1b843797 fix(session): say when the layout came back short
A restored tab whose panes could not be started was dropped with a log
line and nothing else. The layout coming back is the product's headline
claim, so a tab quietly not coming back is exactly the thing that needs
saying — a broken shell or an unreachable remote silently thinned the
window and left no trace on screen.

The restore now counts what it dropped. On launch the count lands on the
home screen next to whatever else went wrong; switching workspaces
raises it as a notification, since there is already a window to read.
2026-08-08 05:29:00 +08:00
l0ng-ai 66d4d03d33 fix(themes): say which files in the themes folder did not load, and why
A theme file that fails to parse logged a warning and then simply was
not in the list. The themes folder is one the app tells you to open and
drop files into — "it isn't there" needs a reason attached to it, and a
log file is not where anyone looks for one.

The loader now carries its rejections out with the themes, and the theme
picker lists each file name with the parser's reason above the presets:
broken.yaml — missing field `accent`.
2026-08-08 05:20:59 +08:00
l0ng-ai 4406340974 fix(ui): make the two card overlays look as modal as they behave
The worktree prompt and the SSH auth prompt each cover the window with a
full-bleed layer that swallows every click — and drew nothing, so the
app simply stopped responding with no sign of why. The palette and the
switcher already answer this with presets::scrim_fill; these two just
never used it.

Both now dim what is behind them. The worktree prompt also cancels on a
click outside, the same gesture the palette and switcher take. The auth
prompt deliberately does not: a mis-aimed click would abandon a
handshake mid-flight, so Escape stays its only way out.
2026-08-08 05:15:07 +08:00
l0ng-ai 90cf1c0d7a Drop the two SSH close helpers the general close guard superseded 2026-08-08 05:06:16 +08:00
l0ng-ai e86cb44923 Merge branch 'fix/ssh-in-menu-bar' into integration/polish 2026-08-08 04:50:37 +08:00
l0ng-ai 12566d9b7f Merge branch 'fix/sidebar-search-branch' into integration/polish 2026-08-08 04:50:37 +08:00
l0ng-ai 6cab19ce01 fix(menu): put SSH in the menu bar, and stop offering Full Screen twice
SSH is one of the reasons to pick tty7 and it had no entry in the menu
bar at all — the only routes were ⌘P and Settings, both of which you
have to already know about. Manage Profiles and Reconnect now sit in
File beside the other things that open a pane; Remote Files and Port
Forwarding sit in View beside the other panel toggles. Same labels as
the command palette, so there is still one name per thing.

AppKit adds its own "Enter Full Screen" to the bottom of any menu named
View, so ours sat directly above it: the same command listed twice under
two different shortcuts. Ours is gone; ⌘↵ still works and is listed on
the Keybindings page. Drops the AppMenuEnterFullscreen key with it.
2026-08-08 04:50:14 +08:00
l0ng-ai 7f3f69dfd6 fix(sidebar): search what the row shows, not just the part that fits
Each sidebar row shows a truncated title and a branch. The filter read
only the truncated title, so typing the branch you can see — or the part
of the path the row elided — matched nothing, and the list emptied out
on a query that is plainly on screen.

It now matches the full title and the branch as well as the label.
2026-08-08 04:27:23 +08:00
l0ng-ai 9c929fff91 Merge branch 'fix/unbindable-actions' into integration/polish 2026-08-08 04:21:36 +08:00
l0ng-ai 9ebbe2cd24 Merge branch 'fix/io-error-messages' into integration/polish 2026-08-08 04:21:36 +08:00
l0ng-ai e7093e66fb fix(keymap): make the three panel actions bindable, and stop dropping typos in silence
ShowRightPanelInfo, ShowRightPanelChanges and ShowRightPanelFiles have a
make_binding arm and are dispatched like any other action — but they had
no slot in default_bindings, which is the only list set_binding writes
into. So `"ShowRightPanelInfo": "ctrl-1"` in a hand-edited config.json
went into a branch that did nothing, and the Keybindings page, which
reads that same list, never showed them at all.

They now have empty default slots, so they appear under View and can be
bound like anything else. A binding naming an action that does not exist
— a typo, or a name from an older build — now says so in the log instead
of vanishing.

every_dispatchable_action_has_a_slot_to_bind_it_in keeps the two lists
from drifting apart again.
2026-08-08 04:11:19 +08:00
l0ng-ai 63ad270473 fix(errors): give the failures people can act on a sentence
Every host operation that fails renders as "{context}: {raw io::Error}".
Display on an io::Error answers "what happened" for someone reading a
log; it does not answer "what now" for the person who just lost a save,
and "Permission denied (os error 13)" is the shape of that gap.

The six kinds that change what you would do next — denied, gone, no
space, read-only, busy, timed out — now read as one authored sentence in
all three locales. Everything else keeps its raw detail rather than
losing it to a vague house message.

Also: "Save failed" did not say which file, and with more than one
editor tab open that is the first thing you need to know. It is now
"Could not save {name}".
2026-08-08 04:00:34 +08:00
l0ng-ai c2dcfe445f Fix the merged i18n guard: one ALL sweep, not two half-tests 2026-08-08 03:51:59 +08:00
l0ng-ai 2a9b4764bd Merge branch 'fix/first-terminal-failure' into integration/polish 2026-08-08 03:48:43 +08:00
l0ng-ai 70c82376bd Merge branch 'fix/ssh-advanced-grouping' into integration/polish
# Conflicts:
#	src/ui/i18n/mod.rs
2026-08-08 03:48:43 +08:00
l0ng-ai 6ad7a25e0b Merge branch 'fix/sftp-overwrite' into integration/polish
# Conflicts:
#	src/ui/i18n/mod.rs
2026-08-08 03:48:43 +08:00
l0ng-ai 0d698f21a3 Merge branch 'fix/keybind-recording-feedback' into integration/polish 2026-08-08 03:48:42 +08:00
l0ng-ai 966f3bebb9 Merge branch 'fix/switcher-column-hint' into integration/polish 2026-08-08 03:48:42 +08:00
l0ng-ai 43450c075e Merge branch 'fix/stacked-daemon-prompts' into integration/polish 2026-08-08 03:48:42 +08:00
l0ng-ai aa97ec94a2 Merge branch 'fix/i18n-enum-guard' into integration/polish
# Conflicts:
#	src/ui/i18n/mod.rs
2026-08-08 03:48:42 +08:00
l0ng-ai 78ca226afa Merge branch 'fix/settings-dirty-escape' into integration/polish 2026-08-08 03:48:42 +08:00
l0ng-ai 60d994c337 Merge branch 'fix/ssh-auth-error' into integration/polish 2026-08-08 03:48:42 +08:00
l0ng-ai 5a6f4ae611 Merge branch 'fix/keybindings-page' into integration/polish
# Conflicts:
#	src/ui/i18n/en.rs
#	src/ui/i18n/ja.rs
#	src/ui/i18n/mod.rs
#	src/ui/i18n/zh.rs
2026-08-08 03:48:42 +08:00
l0ng-ai 7e441921a4 Merge branch 'fix/destructive-confirms' into integration/polish 2026-08-08 03:48:41 +08:00
l0ng-ai f605a21e76 Merge branch 'fix/copy-consistency' into integration/polish 2026-08-08 03:48:41 +08:00
l0ng-ai 4e5d711e5d Merge branch 'fix/agent-status-affordance' into integration/polish
# Conflicts:
#	src/ui/presets.rs
2026-08-08 03:48:41 +08:00
l0ng-ai 82947a2510 Merge branch 'fix/tree-states' into integration/polish 2026-08-08 03:48:41 +08:00
l0ng-ai 5e171d8f6b Merge branch 'fix/palette-list' into integration/polish 2026-08-08 03:48:41 +08:00
l0ng-ai 1ad76c153a Merge branch 'fix/icon-hit-targets' into integration/polish 2026-08-08 03:48:40 +08:00
l0ng-ai be228d22cf Merge branch 'fix/settings-search' into integration/polish 2026-08-08 03:48:40 +08:00
l0ng-ai 3caf45108d Merge branch 'fix/cursor-opacity' into integration/polish 2026-08-08 03:48:40 +08:00
l0ng-ai 08ad86756a Merge branch 'fix/terminal-context-menu' into integration/polish 2026-08-08 03:48:40 +08:00