Three layouts collapsed in fe028a9, where the isMacChrome branches were
removed by a scripted edit that was wrong in two ways. The *bindings* were
dead by construction; the layout classes inside them were not, and nothing
replaced them.
- tab-bar: the edit appended a second `class` attribute to an element that
already had one. A duplicate attribute is silently dropped, so the
workspace strip lost h-9/pt-1/pl-1.5 entirely and collapsed under the
title bar. Merged into one attribute, with a comment saying why there is
exactly one. A template sweep found no other element carrying two.
- connection-manager + editor-window <header>: the edit deleted all five
[class.h-*]/[class.pl-*] bindings and put nothing back, leaving both
headers with no height and no left padding. Restored h-9 pl-3 (the
non-mac arm) and dropped the now-false "on macOS this row IS the
titlebar" comments — every window is frameless now.
Separately, the ListView never filled its panel: 7.css sets table-layout:
fixed but declares no width, so the table shrank to content and left a dead
grey gutter down the right of each pane. w-full is safe as a utility
precisely because 7.css declares no width for `skin` to outrank it with;
the unsized first <col> then absorbs the slack, which is what makes Name
elastic.
Verified on a live window capture, not from a build log: the tab strip has
its height back and both tables reach the pane edge. The connection-manager
window needs a click to reach and is unverified visually — its fix is a
verbatim copy of the editor-window one.
Co-Authored-By: Claude <noreply@anthropic.com>
The dual-skin arrangement existed to let one build toggle between the modern
shadcn look and 7.css. Keeping both correct is what forced the cascade tricks
that fight 7.css, so the branch drops the toggle: this build IS the Windows 7
build.
- `skin` is gone from AppSettings/DEFAULT_SETTINGS, the settings store, the
SettingsService (signal, applySkin, persist payload), the options popover
control, and the i18n keys.
- buildPreload adds `.win7` unconditionally; `chromeOptions()` no longer takes a
skin and always returns frameless `titleBarStyle: "hidden"`.
- `IS_WIN7_CHROME` and `IS_MAC_CHROME` are deleted along with every
`isMacChrome` branch in the tab bar, editor window and connection manager.
Those reserved `pl-24` for macOS traffic lights, which frameless chrome
removes on every platform, so the branches were dead by construction.
- The title bar is no longer conditional — it is the window's only chrome.
- index.html keeps just the win7 anti-flash arm (four theme states).
Not done here, deliberately: stripping Tailwind's element styling. The
neutralizers in styles.css (notably `.win7 button { min-width: 0 }`) are still
load-bearing until each component's markup is converted to 7.css's real
contract, so they come out per component rather than in one sweep.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean; bundle
490.50 kB, down from 492.52 kB.
Co-Authored-By: Claude <noreply@anthropic.com>
Evaluation branch: a full Aero skin, including frameless windows with our own
title bar, switchable against the existing modern look.
The app already had a four-axis runtime skinning mechanism and zero component
stylesheets, so the skin is one new setting plus one stylesheet section rather
than a parallel UI. The key move is cascade order: 7.css is imported into a
`skin` layer ABOVE Tailwind's `utilities`, so its bare element rules outrank
the ~1900 inline utility classes already in the templates. Template changes are
limited to additive, skin-inert hooks (ARIA roles, `has-scrollbar`,
`w7-toolbar`, `w7-btn`).
- styles.css: explicit layer order, `7.css/dist/7.scoped.css` via `layer(skin)`,
a token bridge re-pointing the existing semantic tokens at Win7 values (no new
tokens, so `@theme inline` is untouched), a hand-authored dark Win7 palette,
and neutralizers that `revert-layer` 7.css's dialog-button metrics back to the
utility layer. Neutralizers must live inside `skin`, not `skinfix` —
`revert-layer` only reaches `utilities` from one layer up.
- Frameless chrome: `chromeOptions(skin)` is now the single place either skin's
frame is decided. `titleBarStyle: "hidden"` on every platform, which is also
the only way to drop the macOS traffic lights. New `title-bar` component
mounted by all three window roots.
- Window controls over RPC (minimize/toggle-maximize/close/is-maximized), served
per window kind so the X reuses each one's existing close path — editors still
get their dirty prompt. electrobun has no maximize event, so the state is
derived from `resize`; double-click-to-maximize is implemented here too.
- Font: Noto Sans vendored (OFL-1.1) under ui/src/fonts, since Segoe UI is
absent off Windows and not redistributable.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (486 kB initial,
under the 500 kB budget), app launches with the skin active and no console
errors. The rendered result has not been eyeballed — screen capture and
accessibility are both denied to the shell on this machine.
Co-Authored-By: Claude <noreply@anthropic.com>
The dark palette was shadcn's cool zinc (#202023, hue ~286). Anchor it on
#1a1a19 instead — cds gray-830, a warm near-neutral (hue 107) — and retone
the whole dark neutral family to match, since a warm background under cool
panels clashes. Lightness ramp is preserved; chromatic tokens (destructive,
success, icon hues, accent formulas) and the light palette are untouched.
Also introduce --chrome so the app isn't one flat tone: frame strips (tab
bar, status bar, transfer bar, editor/manager header+footer, connection
sidebar) now sit one step below the content panes, with popovers and hover
above them — the standard dark-UI elevation order.
Dark ramp: #151514 chrome, #1a1a19 content, #20201f card, #2e2e2d hover;
the "darker" tone drops chrome to true black. Light gains only a zinc-50
chrome step.
Co-Authored-By: Claude <noreply@anthropic.com>
Wires electrobun's Updater to the /dl download host. The packaged app checks
https://miniscp.com/dl on launch + every 6h; when a newer build exists, an
accent-colored "update tool" appears next to Connections in the tab bar. It
announces the update, becomes its own inline progress bar while downloading,
then flips to "Restart to apply" (two-step: download, then user-triggered
relaunch).
- electrobun.config.ts: release.baseUrl = https://miniscp.com/dl (baked into
version.json at build; miniscp.com fronted by an external proxy for TLS).
- rpc.ts: checkForUpdate/downloadUpdate/applyUpdate requests + updateStatus push.
- rpc-handlers.ts + index.ts: call Updater; poll on launch+6h; stream
onStatusChange progress. checkForUpdate self-guards the dev channel.
- rpc.service.ts + new updater.service.ts: signal-based state
(available -> downloading -> ready, ready derived from downloadUpdate ok).
- tab-bar.ts: single bg-primary pill; progress fills the pill itself.
- icons.ts: register mynaDownload. i18n: tabBar + menu keys across all locales.
- menu.ts + app.ts: "Check for Updates…" app-menu item -> UpdaterService.check().
- docs: build-and-release + architecture updated.
Unsigned builds still update (applyUpdate strips the macOS quarantine xattr).
Verified: tsc --noEmit (exit 0) + build:ui AOT (exit 0). Live pill + full
download/apply flow require a two-build test on the host.
Co-Authored-By: Claude <noreply@anthropic.com>
Custom signal-based runtime (no i18n dependency): shared src/shared/i18n
dictionaries, ui I18nService.t() reactive by construction so language
switches live, and a backend i18n.ts for native menus + thrown errors.
Every user-facing string keyed across templates, toasts, dialogs, file
kinds, columns, macOS menu/context menus, and errors. Language picker in
Options (auto-detects OS locale, falls back to English); switching
relabels the webview and native chrome instantly. English/Spanish/French/
German fully drafted; Chinese/Japanese/Basque drafted, flagged for native
review. Docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local panels are fs.watched (LocalDirWatcher + setWatchedDirs -> pathRefresh);
remote panels poll every 3s. New refresh({silent}) skips the loading/busy
flags and touches no DOM, snapshotting sessionId+cwd to discard results if the
user navigated mid-flight. Only the active tab is live; switching tabs
refreshes the new pair. A future on/off setting is left as commented
breadcrumbs. Docs updated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
External editor via cmd/start + exe picker; Permissions hidden for
Windows-local (chmod only flips read-only there); Explorer/Recycle Bin
wording; Ctrl vs Cmd shortcut labels; separator-aware path joins +
drive-root detection; drives list in the panel source picker; icon.ico
+ colored tray icon; icacls ACL on connections.json; README Windows
(beta) section with upstream known issues and a smoke-test checklist.
Runtime paths pending first boot on a real Windows machine.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Build n = commit count, baked at build time into a gitignored
version.gen.ts (postinstall + all build scripts regenerate it). About
lives in the macOS app menu and as a footer row in the options popover
(the cross-platform path), showing build, commit, date and engines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single window centered over the main app. Electrobun has no native
modality, so it's approximated: bun bounces the main window's focus back
to the manager, the main webview shows a click-swallowing overlay, and
main-targeted menu/keyboard actions are dropped while open. Editor
windows stay fully usable. Connect/open-stack execute in the main
window via the existing connect: action channel; store edits push a
connectionsChanged refresh. Trigger moved from the panel dropdown to a
tab-bar button.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tone row appears under Theme when the effective theme is dark.
Grey palette uses macOS-like #26262a background.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Action buttons, server picker, and its dropdown now follow the size
setting via new --action-h/--action-font/--action-icon variables.
Default tier matches the old fixed sizes; tab bar stays fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Built-in editor (default): large modal with monospace textarea, Cmd+S save,
dirty guard; reads/writes through the provider streams so it works on local
and remote files identically (2MB cap with a friendly error)
- External editor: remote files download to appDataDir/edits/<id>, open via
macOS `open -a`, the temp DIR is watched (survives atomic rename-saves) and
every save enqueues a normal upload job (progress/errors in the transfer
list); watchers + temp dirs are cleaned when the session disconnects
- Editor setting: Built-in | Custom with a NATIVE app picker (electrobun
openFileDialog, /Applications, .app filter) and reset-to-default
- Leftover temp files from a crash prompt for cleanup at boot
- F4, toolbar button (mynaFileText), and File > Edit File (Cmd+E) menu entry
- README: feature notes + Portability notes (open/start/xdg-open seam
documented in code and README per user request)
Verified headless against the box: sftp read/write roundtrip incl. UTF-8,
size cap, watch->save->auto-upload->remote-updated, session cleanup, orphan
listing and clearing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Options popover: 'Finished transfers' Auto-clear | Keep (default auto-clear,
the previous behavior); persisted like the rest
- Transfer list header: 'N active · M finished' + Clear finished button
(dismisses completed/failed/cancelled only, never running/queued)
- Failed rows now name the file that failed alongside the error message
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tabs live in their own min-w-0 overflow-x-auto container (scrollbar hidden,
trackpad scrolls); the new-tab button and the options gear sit outside it and
stay pinned. The active tab scrolls into view when it changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tabs are draggable within the strip: HTML5 DnD with the WebKit setData quirk
handled, an inset primary insertion line at the drop position (no layout
shift), and the dragged tab dimmed. Reordering only touches the workspaces
array order; ids, sessions, and the active tab are unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transfers: Single | Multi in the options popover, with an 'At once' picker
(2/3/4/6/8) when Multi. Engine concurrency becomes an instance field applied
live: at boot from settings.json and on every saveSettings (raising the limit
starts queued jobs immediately). Verified headless: 3 slow jobs overlap at
concurrency 3, stay strictly serial at 1, clamp works.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Segmented row in the options popover; applied via data-text on <html> driving
CSS variables (--list-font / --list-meta-font / --path-font), persisted in
settings.json like theme and speed unit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Overwrite confirmation: name-collision detection before enqueue; dialog shows
source vs destination (size, mtime, 'newer' badge) with per-item Skip/Replace
and batch Skip all / Replace all; type mismatches auto-skip with a toast;
cancel aborts the whole operation
- Transfer speed + ETA: UI computes smoothed bytes/sec from progress deltas
(callback bus makes deltas meaningful); shown in the expanded queue rows and
the status-bar summary; unit follows the speed-unit setting
- Settings: bun-side SettingsStore (settings.json, 0600, atomic) + get/save RPC;
SettingsService; gear popover on the tab row (theme segmented control, speed
unit); persisted across restarts
- Light theme: data-theme on <html> drives token overrides; absent = follow OS
- macOS fused titlebar: hiddenInset + trafficLightOffset, tab row becomes the
draggable titlebar (app-region drag/no-drag classes); other platforms keep a
separate titlebar (gated by a platform value injected via window preload)
- README: known-issues note for the upstream Bun/Electrobun FFI crash
Verified: tsc + ui build clean; SettingsStore roundtrip + 0600 headless;
app boots and the UI fetches settings over RPC at startup.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Freeze root cause: TransfersService consumed transferUpdate through an effect
that both read and wrote the jobMap signal — the first progress message spun
the effect into an infinite re-run loop, pegging the webview thread while the
copy completed fine bun-side. Push messages now flow through plain callback
registries on RpcService (also stops signal coalescing from dropping progress
snapshots). Verified live: a 60MB boot-time self-test copy streamed progress
into the running UI and the post-completion panel refreshes arrived.
- Tabs: workspaces (left+right PanelState pair each) in PanelsService; tab bar
replaces the header; sessions persist in background tabs; Cmd+T/Cmd+W/Ctrl+Tab;
closing a tab disconnects its sessions; last tab can't close
- Per-panel toolbar (top, icon-only): direction-explicit copy (-> / <-),
rename, new folder/file, chmod, delete; panel footers removed
- VSCode-style status bar: transfer summary (spinner/percent/queued/failures,
click to expand the job list) + active panel info; function bar removed
- transfer details list is collapsible, auto-expands on failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>