Commit Graph
1799 Commits
Author SHA1 Message Date
Kang eebb7e50e5 feat(security): use real masked password inputs 2026-07-27 17:07:19 +08:00
Kang 300e789885 feat(session): use real dialog text inputs 2026-07-27 17:07:19 +08:00
Kang c7ba80f169 feat(ssh): use a real temporary link input 2026-07-27 17:07:19 +08:00
Kang 2d2abd5571 feat(sync): use real text inputs for group editing 2026-07-27 17:07:19 +08:00
Kang 3db088a244 feat(ui): replace simulated search inputs 2026-07-27 17:07:19 +08:00
Kang 2778e23d12 feat(sftp): make file search a real input 2026-07-27 17:07:19 +08:00
Kang 339e4c8fe7 feat(sftp): make the path bar a real input 2026-07-27 17:07:19 +08:00
Kang 262b9a5ba8 feat(sftp): add breadcrumb directory navigation 2026-07-27 17:07:19 +08:00
Kang 786b75fe5c fix(sftp): preserve root when joining remote paths 2026-07-27 17:07:19 +08:00
Kang 1438cb10e3 fix(sftp): restore stable browser navigation state 2026-07-27 17:07:19 +08:00
Kang 794e54c87e feat(sftp): add hidden file visibility control 2026-07-27 17:07:19 +08:00
Kang a67be6e41e feat(header-status): implement header status feature with settings and localization 2026-07-27 10:16:14 +08:00
Kang 6e5d811c82 style(settings): fix style for app settings 2026-07-27 09:51:32 +08:00
Kang cea0bb3dd4 feat(terminal): give the right-click menu its icons
Tauri's terminal context menu leads every row with a Material glyph —
copy, search, search-online, AI, translate, paste, paste-selected,
clear, clear-all, select-all. Ours had none, which is most of why the
two menus do not read alike.

Every row now carries one, including the submenu triggers and the rows
inside them, and a row without an icon still reserves the column so the
labels line up. Six glyphs were missing from the bundle and are vendored
through the manifest like the rest.

Sending a prompt to the AI panel from this menu — or from anywhere else
— now writes it into the composer as well as the draft. The composer
owns its own buffer since it became a real input, so the two would
otherwise show different things.
2026-07-27 09:23:48 +08:00
Kang 2870204445 feat(sftp): real inputs across the file manager's dialogs
The new-file, symlink, move and inline-rename boxes were the last
hand-rolled inputs in the app — each a label div over a draft string
with its own key handler, and the symlink one with a focused-field enum
to decide which of its two boxes a keystroke belonged to.

They are real inputs now. The handlers keep only the keys the dialog
owns, and the remote-name and link-target length limits moved with the
text into the one place that writes it.

Not verified in the running app: the file manager needs an SSH session
and no server is reachable from this machine.
2026-07-27 09:06:34 +08:00
Kang b6dcdcdd26 feat(inputs): finish the sweep — no fake text inputs left
The last of them: the quick command variable prompt and category rename,
the AI ask panel's composer and model search, and the process manager's
nice value. `transfer_input` is deleted, and with it the focus handle
each fake input carried — `NyaTermApp` and the feature states lost
fourteen of those, along with nine key handlers and the focus-steering
they needed.

The AI composer takes newlines now, the way Tauri's does, with Enter to
send and Shift+Enter for a line break.

One more GPUI trap, recorded with the others: a box is content-sized,
and an empty field's content is nothing, so it renders as a ~30px square
that wraps its placeholder a character or two per line. A flex column
stretches its children, so most callers were fine; a `flex_none` control
slot and a plain block parent were not.
2026-07-27 08:53:21 +08:00
Kang c0eb425659 feat(settings): real inputs for the remaining settings rows
Word separators, the X11 display, the master password, the recording
and download paths, the default editor command, and the three boxes on
each AI credential card. Same shape throughout: a real box, the key
handler that stood in for typing gone, and only the keys the form itself
owns left behind.

The credential rows had to stop iterating the credential list in place —
each row builds three inputs, and creating one needs the app mutably.
2026-07-27 08:10:57 +08:00
Kang af2578e2f0 feat(settings): real inputs for AI, translation, cloud sync and the filters
Four helpers between them covered most of what was left: the AI settings
field, the translation field, the cloud sync field and the three filter
boxes (active sessions, Docker, processes). Each hosted a label div over
a draft string with its own key handler; each is a real input now, and
the handlers are gone.

The field enums carry their own input id, so an edit finds its way back
to the right draft without an editor tracking a focused field, and the
reverse lookup scans the variant list rather than a second match that
could drift from the first.

Secrets stopped being displayed. `cloud_secret_display` rendered a row
of asterisks sized to the draft, or the word "set", into the place the
text goes; the boxes mask themselves now and hold the draft, and whether
a secret is stored is already badged beside each provider.
2026-07-27 08:01:32 +08:00
Kang 9bbe1f5689 docs(architecture): update what is left of the fake inputs 2026-07-27 04:09:22 +08:00
Kang 68cab0beea feat(security): real inputs across the four security editors
SSH keys, saved passwords, OTP entries and credential rules all shared
one label-div field helper and one hand-written key handler each. The
helper now hosts a real input, the handlers keep only the keys that
close or save the editor, and each editor's boxes are dropped when it
opens or closes so the next one seeds from its own values.

Secrets stopped pretending. A stored password used to render as a row of
bullets sized to the draft, or as the literal words "unchanged" sitting
where the text goes; the box masks itself now and says "unchanged" in
its placeholder, and the reveal toggle unmasks the box rather than
swapping the string. The numeric OTP fields keep digits only.

An empty field also stopped seeding itself with a space — the old
helper needed one to keep its row height, and it showed up as a single
bullet in every empty secret box.
2026-07-27 04:08:56 +08:00
Kang c6743acd67 feat(send-command): make the command box a real text area
Tauri's is a textarea: Enter is a newline and Ctrl/Cmd+Enter sends. Ours
sent on Enter and had no way to type a second line, which the "Line by
line" send mode exists for. It now wraps, takes newlines, and sends on
Ctrl/Cmd+Enter; Escape still clears.

Hex is normalised as it is typed — digits regrouped into pairs, anything
else dropped — and the box is written back with what the draft holds, so
the two cannot drift apart.
2026-07-27 03:50:02 +08:00
Kang f7da0cddac docs(architecture): record the text input migration and its traps
Where the real inputs have landed, how many label-div "inputs" are left,
and the three GPUI behaviours that make converting one look like it
worked when it did not.
2026-07-27 03:42:46 +08:00
Kang 9f1ca5a58e feat(quick-commands): real inputs in the command editor
Label, description and the script box are real inputs now. The script
box takes Enter as a newline, so the dialog's Enter is unambiguously a
save, and paste goes through the box rather than a hand-written
clipboard branch.

Two things this turned up:

- The editor surface grabbed focus back on every click, exactly as the
  connection editor used to, so a click could never land on a field.
- A wrapped box has to stretch the row that holds it. The field asks for
  its parent's height, and against an indefinite one the percentage
  resolves to zero — the box rendered as an empty rectangle that could
  not even be clicked.
2026-07-27 03:40:51 +08:00
Kang 1a7f781e62 feat(sftp): real input in the new-folder dialog
The first of the file manager's dialogs onto the shared input. The name
box takes a caret, a selection, IME and the clipboard; the dialog keeps
Escape and Enter, and the 255-character remote-name limit moved with the
text into the one place that writes it.

Not verified in the running app: the file manager needs an SSH session
and no server is reachable from this machine.
2026-07-27 03:23:31 +08:00
Kang d7ae01953d feat(network): real input in the group dialog
The last of the network panel's three dialogs. Same shape as the other
two: a real box, no hand-rolled key handler, Escape and Enter owned by
the dialog.
2026-07-27 03:19:40 +08:00
Kang e32b03bafb feat(network): real inputs in the proxy dialog
The same treatment as the tunnel dialog: the boxes take a caret, a
selection, IME and the clipboard, the key handler that stood in for that
is gone, and Escape and Enter belong to the dialog.

The password box masks itself, so the "keep the stored one" hint moved
from the value to the placeholder — it used to render a row of asterisks
sized to the draft, in the place the text would have gone.
2026-07-27 03:12:08 +08:00
Kang 95466ead38 feat(network): real inputs in the tunnel dialog, and room to show them
The tunnel dialog's boxes are real inputs now, so they take a caret, a
selection, IME and the clipboard, and the hand-rolled key handler that
stood in for all of that is gone. Escape and Enter moved up to the
dialog, which is where they belonged: the boxes deliberately leave both
unconsumed.

The dialog also has somewhere to be. It was drawn inside the network
panel, and an overlay fills its nearest positioned ancestor — a panel a
couple of hundred pixels wide — so a 640px form was squeezed into it
until every caption wrapped onto three lines ("Tunn el Type"). The
network dialogs are now hosted by the root, the way the transfer
overlays already were.
2026-07-27 03:02:24 +08:00
Kang f8fe4ef16b feat(settings): give the panels a real text input to share
Outside the connection editor every "input" in the app is a label div
over a draft string, with a focus handle per box and a hand-written key
handler — no caret, no selection, no IME, no clipboard, and a little of
it reimplemented in each panel. The search-engine editor showed the
failure plainly: the placeholder and the value were stacked inside one
box that only had room for one of them.

The connection editor solved this by owning a TextField entity per
field. Threading that map through every panel's state would be a lot of
churn, so the fields live in one registry keyed by a string id, created
the first time a panel renders one. A panel keeps no state beyond the
value it already had, and edits arrive as one event with the id
attached, which the registry routes by prefix.

The search-engine editor is the first caller: its two boxes are real
inputs now, and the key handler and focus-steering they needed are
gone. Structural changes forget the ids they invalidate — adding an
engine shifts every row's index.
2026-07-27 02:30:31 +08:00
Kang 41d8e1c314 fix(tabs): keep a session tab's title on one line
The title had `overflow_hidden` but nothing stopping it from wrapping,
so it laid itself out as a column of a few characters each and the tab —
one row tall — showed whichever line happened to land there. A local
PowerShell session read "ste", out of the middle of "System32".
2026-07-27 02:17:04 +08:00
Kang bc4dc3db4b fix(terminal): stop CJK rows from collapsing into themselves
A shaped terminal row is laid out with `force_width`, which puts glyph
*n* at `n * cell_width` no matter how wide the glyph is. A CJK character
covers two terminal columns, so every character after one was pulled a
cell to the left and the row piled up on itself — a line of Chinese was
unreadable, and so was the PowerShell banner.

Each double-width character is now followed by a space, so one glyph is
one cell again. The space advances the glyph index without painting, and
a cell's background is drawn as a rect underneath either way. Both row
paths needed it: the styled one and the plain-text fast path.
2026-07-27 02:13:37 +08:00
Kang 380ab306cf feat(connections): lay the editor's form out the way Tauri does
Captions move above their boxes, so a field's whole width is what was
typed into it rather than half a line under a label. Host, port,
username and the serial port carry the red required marker, the fields
that had one in Tauri show their placeholder again, and the host row
gives the host the room while pinning the port at a fixed width instead
of splitting the row down the middle.

The port and the other numeric fields gain the spinner Tauri has, with
the range each one is actually valid over. Typing still works — the
buttons are a shortcut, not the only way in.

Switching the connection kind rewrites the default port on the draft;
the box now hears about it, so Telnet opens on 23 rather than showing
22 while the draft says otherwise.
2026-07-27 02:00:19 +08:00
Kang aea6ed3354 feat(connections): drive the editor's selects from the keyboard
An open select now takes focus, so Up/Down walk the options, Home/End
jump to the ends, Enter takes the highlighted one and Escape closes
without changing anything. The highlight is shared with the pointer, so
moving the mouse does not leave a second, stale one behind, and it
scrolls itself into view in a long list.

The keys are answered by the popover rather than the editor surface,
because only the popover knows the option order — the choices are built
where they are rendered, and mirroring them into the runtime would be a
second source of truth. That is also why the field map handed to the
sections became a struct: reading the app entity again from inside its
own render panics.

Two things this uncovered:

- Every select rendered its current value into a box that collapsed to
  zero width, so all of them looked empty. Giving the value a flex basis
  brings back "None", "COM1", "8" and the rest.
- Enter in the group popover's "new folder" box now creates the folder
  again, and the box is emptied afterwards — it holds its own buffer, so
  clearing the draft's copy left the typed name on screen.
2026-07-27 01:43:12 +08:00
Kang 56ab3daa43 feat(ui): let the text field wrap and hold more than one line
The description box was the last field still faked with a label div,
because the widget could only lay out a single shaped line. It now
shapes through `shape_text`, so a field opted into `multi_line` wraps at
its own width, grows downward, scrolls vertically instead of sideways
and takes Enter as a newline rather than handing it to the dialog.
Up/Down move by hard line, keeping the column.

Space arrives without a `key_char` on some platforms — the terminal's
key encoder already names it for the same reason — so it is named here
too, or typing a sentence produced one long word.

Editing any field now clears a stale validation error, and the box is
`flex_none` so the form cannot shrink it into a half-drawn row.
2026-07-27 01:16:35 +08:00
Kang bfbe8beb69 fix(ui): stop the text field from typing Tab and Enter into the buffer
The field treated any key carrying a `key_char` as text. Tab's is "\t" and
Enter's is "\n", so pressing either in a single-line field inserted an invisible
control character instead of leaving the key for the dialog — a Tab typed into
the connection name looked like nothing had happened at all.

Navigation and dialog keys are now named explicitly rather than inferred from
whether the platform produced a character for them.

The description box is left off the editor's field list on purpose: it is the
one multi-line input in the dialog, and this widget is single-line, so it keeps
the legacy key routing until multi-line lands.
2026-07-27 00:25:43 +08:00
Kang 2870c05393 fix(connections): make the editor's selects real popovers
Both editor selects rendered their option list as an ordinary child of the
trigger, so opening one pushed everything below it down the dialog — the group
select shifted the icon, name and host rows every time it was touched — and the
list was clipped by whichever ancestor hid its overflow.

They now open through `deferred(anchored(..))`, the same treatment the context
menus got: laid out against the window, offset below the trigger, snapped inside
the window edges, and painted above the dialog instead of inside its flow.
Clicking outside dismisses only the select that is open, so a stale click for a
different one is ignored.

The options themselves read better: a wider panel that fits a group path or a
proxy summary without wrapping, taller before scrolling, and a tick on the
current value rather than tint alone. The "new group" input inside the group
popover is a real text field now, which is also where its placeholder moved to.
2026-07-27 00:16:43 +08:00
Kang ce1c140f65 feat(connections): give the connection editor real input fields
Every input in the new/edit-connection dialog was a label div sharing one focus
handle, with a `focused_field` enum routing keystrokes to whichever draft string
was "active". Clicking a field only moved that enum, so there was no caret, no
selection, no pointer positioning, no composition, and password masking was a
string of bullets rebuilt on each render.

Each text input is now a `TextField` entity, built when the editor opens and
cleared when it closes, so the widgets live exactly as long as the draft they
mirror. `ConnectionEditorState` stays a plain value the runtime can clone and
save from; the fields write back through their subscriptions. The password field
masks itself while the buffer keeps the secret in the clear, which is what the
save path needs. The folder-name editor gets the same treatment.

Two focus fixes fall out of this. The editor surface's blanket `on_click` focus
grab is gone: it existed to keep the old fake inputs "focused", and would now
steal focus back from whichever field the pointer just landed on, since click
follows mouse-down. And because focus is per-window, the detached editor window
claims the name field itself on its first frame rather than trusting what the
main window focused.

The labelled row is the hit target, not just the one text line inside it, and it
takes its ring from the widget's own focus rather than from an enum nothing
updates any more.
2026-07-27 00:08:37 +08:00
Kang f0655b59c1 feat(ui): add a real text field widget and use it for the connections filter
Every "input" in the app is a label div: a `div` that takes focus, swallows key
events and prints the draft string. There is no caret, no selection, no pointer
positioning and no composition, so the moment anyone tries to edit rather than
append it reads as broken — which is exactly the complaint about the filter box.

`TextField` is a real GPUI widget instead. It is an `Entity` that owns its
buffer and focus, implements `EntityInputHandler` so the platform routes IME and
clipboard through it, and paints through a custom `Element` that shapes the line
once and reuses that shaping for hit-testing, the selection quads and the caret.
Owners learn about edits by subscribing to `TextFieldEvent::Changed`, which
carries the new content, rather than reading back through the entity.

It deliberately does not claim Enter, Escape, Tab or the arrows: those are
dialog and list gestures, so they stay unconsumed for the owner's own key
handler. That is what lets the saved-connections filter keep driving result
navigation while the field handles the text.

The editing rules live in `nyaterm_core::TextEdit` — caret motion across
multi-byte boundaries, what counts as a word (hosts and paths make `.`, `-`, `/`
and `@` stops), and which end of a selection shift moves — so they are testable
without a window.

Adopting it in the filter box retires the placeholder caret drawn by splicing a
`|` into the display string, and the seven arms this panel had bolted onto
`NyaTermApp`'s `EntityInputHandler`; the widget owns its own composition now.
2026-07-26 23:43:35 +08:00
Kang 900e83af73 feat(connections): make the filter box a real input and rebuild the row layout
The filter box was a label `div` with a key handler: no caret, no IME, no
paste, no way to move the insertion point, and an identical border whether or
not it had focus — clicking it produced no visible change at all. It now
follows the pattern the app already uses for its one field with a cursor: a
`gpui::canvas` installs an `ElementInputHandler` over the field's bounds, the
`EntityInputHandler` on `NyaTermApp` gains an arm per method, and the focused
field takes the primary border. Editing is relative to the caret and steps by
character boundary — the previous `pop()` cut a byte off multi-byte input.
Reaching the field's focus state means threading `window` down to the panel
bodies, which every panel now has for the same purpose.

While a filter is active the box also drives the results: up/down walk them and
enter opens the active row, which is drawn with its own wash and ring so it
stays distinct from the selection. The active row is dropped as soon as the
filter stops matching it.

Folders now start collapsed. Seeding the expansion set with every group buried
the folder list; a filter instead opens the folders that still have hits and
restores the prior tree when it clears, once per keyword so collapsing an
auto-opened folder sticks.

The rest of the row work: names get the full width of a horizontally scrollable
list instead of wrapping inside a fixed-height row, the folder count sits
against the right edge, hover actions are connect and edit and appear
immediately rather than after the detail card's delay, and the detail card is a
real tooltip so it hangs outside the panel instead of covering the rows under
it — which retires the 350ms hover-intent machinery and its per-frame poll. The
top selection strip is gone; those actions live in the menus. A folder with no
connections is no longer swallowed by the empty state.
2026-07-26 23:23:00 +08:00
Kang e1e5fb625f fix(connections): place context menus in window space and add move-to-group
Right-click looked dead. The menu stored the `MouseDownEvent` position, which
is window-relative, then positioned itself `absolute()` inside a panel that is
both offset from the window origin and `overflow_hidden` — so it landed at the
wrong place and whatever hung past the panel edge was clipped, which in a
sidebar this narrow was usually all of it. Menus now go through
`deferred(anchored().position(..).snap_to_window_with_margin(..))`, which reads
the position against the window and paints outside the panel's clip rect.

A right-click on the list background had no menu at all; it now offers the
selection actions plus new connection, new folder and import.

"Move to group" was missing everywhere, leaving drag and drop as the only way
to reparent a connection. It hangs off the row menu, the background menu and
the header menu, moving the whole selection when the clicked row is part of it.
`move_connections_into_group` writes the new order once instead of persisting
and refreshing per connection, so the list is never observed half-moved.

The flyout is a *descendant* of the menu rather than a second overlay, so the
menu's `on_mouse_down_out` does not fire and tear the stack down as the pointer
enters it, and it flips to the left when the window edge is close. It lists the
folders indented by depth rather than nesting a hover cascade per level, which
matches the group dropdown in the connection editor.
2026-07-26 23:22:39 +08:00
Kang 8ac5401b8b fix(connections): sort folders too, and order names the way people read them
Cycling the sort button only reordered connections: `sort_groups` ignored
`ConnectionSortMode` entirely, so folders stayed put and the tree looked
unsorted. It now branches on the mode exactly as `sort_connections` does.

Both comparisons also used ASCII-lowercase ordering, which puts
`192.168.142.100` before `192.168.142.13` — and host lists are most of what
this panel sorts. `nyaterm_core::natural_compare` compares digit runs by value
without parsing them, so an arbitrarily long run cannot overflow, and falls
back to the raw comparison when case folding ties so the order stays total.
`visible_connection_ids` follows the same rule, or Shift-range selection would
walk a different list than the one on screen; it also stops descending into
collapsed folders, whose rows are not reachable.

The button now shows which mode is active instead of hiding it in a tooltip:
the glyph switches to sort-by-alpha, flips vertically for Z-A, and takes the
primary tint while a name sort is on.
2026-07-26 23:22:16 +08:00
Kang ebb81fcaa8 fix(ui): paint the svg icons that never declared a colour
`Svg::paint` does `self.path.zip(style.text.color)`, and `compute_style` starts
from `Style::default()`, whose `text.color` is `None`. A parent's
`.text_color(..)` cascades to real text but never to an `svg()` child, so a
glyph without a colour of its own was skipped entirely at paint time — no
error, no fallback, just an invisible icon still taking up layout.

59 call sites were affected: the window minimise/maximise/close controls, tab
close buttons, the title bar, the quick-command toolbar, the transfer browser
and queue, tunnels, settings and the AI panels. Shared button helpers are fixed
first, so most sites are covered by ~10 functions. Where the parent brightened
the icon on hover, the pair becomes `.group(..)` plus `.group_hover(..)`, since
a `.hover()` refinement cannot reach a child either.

`scripts/check-icon-references.sh` now fails on an `svg()` whose chain has a
`.path(..)` but no `.text_color(..)`. It checks depth 0 only: a colour that
lives solely inside `.group_hover(.., |s| s.text_color(..))` still leaves the
icon invisible until the pointer is over it.
2026-07-26 23:21:58 +08:00
Kang 852c14254f perf(terminal): borrow UTF-8 output and park the event consumer
Three costs on the hot output path:

`SessionEncoding::decode` ran every chunk through the incremental decoder and
allocated a fresh `String`, even though UTF-8 sessions — the default and the
overwhelming majority — are already valid UTF-8. Valid input is now validated
and borrowed, with only the trailing bytes of a split multi-byte sequence
carried into the next chunk.

The transport event queue was drained on a fixed interval, so a dedicated
consumer thread traded latency for wakeups. It can now park on the producer's
push, bounded by a timeout so shutdown and periodic bookkeeping stay on
schedule; the UI tick path keeps the non-blocking drain.

Graphics and the terminal snapshot model shed the copies that were feeding
those two paths.
2026-07-26 23:21:58 +08:00
Kang 5efce4e7cf feat(assets): vendor bundled icons from a pinned manifest
Icons were previously hand-added, so their provenance and licensing lived in
folklore and a mis-painted asset was invisible at runtime: GPUI's `svg()`
reduces a file to an alpha mask, and `img()` keeps its pixels, but nothing
complains when the two are swapped.

Assets are now described by `scripts/icons.manifest` and vendored by
`scripts/sync-icons.sh` from pinned upstream releases, keeping the tree
reproducible and reviewable. `icons/**` stays monochrome and `color/**` full
colour; `scripts/check-icon-references.sh` fails the build when a referenced
asset is missing or painted through the wrong element, and runs in CI.

The lookup tables move out of `formatting/` into a dedicated `features/icons`
module so a ~1000-line brand table is not rebuilt inside a render closure, and
saved connections gain `icon_auto_detect`, which fills in a blank icon from the
detected remote system without ever overwriting a deliberate choice.
2026-07-26 23:21:58 +08:00
Kang 7b12c5492f docs(architecture): record the unwired capabilities left after the sweep
Desktop dead-code warnings went 104 to 18. Name what the remaining 18
are, with the evidence that each is an unfinished feature rather than
cruft -- mostly tests covering code no UI reaches, and match arms that
still handle prompts nothing raises. This is a to-do list, not a
backlog of deletions.
2026-07-26 18:35:40 +08:00
Kang c5b18abee1 refactor(desktop): delete dead fields left by the removed render layer
command_search_draft and the terminal_surface_paint_count field were
only ever written; the live paint counter is the free function of the
same name. ProcessTableLabels lost its five column headers along with
process_table_header, and ConnectionIconDef lost the glyph fallback the
SVG path replaced.

Stopped short of the fields and variants that are not cruft: dropping
SnapshotPasswordPromptKind::CloudPush and friends would have deleted 35
live match arms that still know how to handle a prompt nothing raises
any more. That is an unfinished feature, not dead weight, and removing
it is a product call.
2026-07-26 18:35:02 +08:00
Kang bfab0940a3 refactor(desktop): delete the dead items name matching could not clear
Eighteen more items rustc reported dead that the automated pass had to
skip: helpers whose only remaining mentions were pub use re-exports
(sorted_quick_commands, tunnel_mode_label, compact_transfer_job_row,
now dropped from their mod.rs lists too), and items whose names collide
with live ones elsewhere -- next, all, title, placeholder, line_count,
select, session_id, and a second icon_action_button in the process
table that shadows the live one in the connections list.

TerminalSelection::new was in that batch and is not dead; a generic name
made the reference scan miss decorations.rs and terminal_surface_entity.
It is kept. One doc comment pointing at the deleted terminal_line_element
now refers to the paint crate instead.
2026-07-26 18:28:24 +08:00
Kang e8b8ff231e refactor(desktop): delete the superseded migration render layer
133 items that nothing reaches: the old left_*_panel / right_*_panel
render tree superseded by panel_body's *_view dispatch, the widget
helpers only it called, and the handlers only those widgets invoked.

Verified against the live paths first rather than trusting dead_code
alone: for example start_sftp_download_job survives only as a caller of
start_sftp_download_job_for_target, which the browser selection flow
uses directly.

Deleted iteratively, requiring that no source line outside the items
being removed mentions the name -- rustc's dead set is per compilation
unit, so an item dead in the lib build can still be used from cfg(test)
or re-exported.
2026-07-26 18:21:04 +08:00
Kang 75e1c60165 docs(architecture): record the ai.rs domain split
ai.rs is down from 4,032 to 1,554 lines across providers, agent, risk
and settings, so drop it from the large-file table. Note why risk is
deliberately its own small module, and that serde default_* functions
constrain how far a settings type can move from them.
2026-07-26 18:00:51 +08:00
Kang 7abad78916 refactor(core): split settings defaults and masking out of ai.rs
Move the twenty default_* serde backing functions, the AiSettings
Default impl, the legacy profile migration and the mask/merge secret
helpers into ai/settings.rs. ai.rs imports the default_* names back so
the serde attributes on the settings types still resolve.

Field defaults, the legacy profile migration and which fields count as
secrets are compatibility surface and are unchanged.
2026-07-26 17:59:55 +08:00
Kang 03d2abd4c6 refactor(core): split agent protocol and command risk out of ai.rs
ai/risk.rs holds command risk classification -- the pattern lists, the
rm/dd special cases and the label mapping. These decide whether the
agent may run a command unattended, so keeping them in one small module
makes the rules reviewable on their own.

ai/agent.rs holds the agent loop protocol: the three provider tool
schemas, the reply parsers with their fallbacks, the execution policy
and the agent prompt builders.

The pattern lists, escalation rules, tool schemas, parse fallbacks and
execution policy are unchanged.
2026-07-26 17:57:41 +08:00