The menu now renders 7.css's real contract — ul[role=menu] > li[role=menuitem]
> button for actions, input[type=checkbox] + label for the checkable column
toggles, and dividers as .has-divider on the PRECEDING item (7.css draws them
as an ::after bevel, not as a node; the service's separator entries collapse
onto their predecessor at render time). With the markup right, the framework
supplies everything the hand-written version imitated or lacked: the left
gutter stripe, bevelled separators, the gradient selection band, the checked
box, and disabled/submenu styling we previously had none of.
Deleted: the w7-menu lookalike (both the button-slab stripper in `skin` and
the whole hand-authored Aero set in `skinfix`) and its w7-menu/w7-menu-sep
hooks. The "7.css has no menu component" claims in styles.css and
architecture.md were FALSE — dist/gui/menu.css is a full component; the old
markup (div > button) simply matched none of it. Both corrected to state the
real contract and the trap.
Keyboard now follows the platform instead of a highlight class: arrows move
REAL focus between items — 7.css styles :focus-visible identically to :hover,
and keyboard-initiated programmatic focus counts — and Enter/Space activate
buttons natively; only labels need a synthetic click. This also makes the menu
honest to screen readers.
Dark mode: menu.css hardcodes light literals (color:initial, white bevel
halves, the #b3d3f9 checked-box ring); restated in skinfix's dark arm like the
ListView's.
Also fixed, caught BY the harness: the Stage 3 frame sizing rule was keyed on
bare `.window`, forcing 100vh height onto every 7.css window (the harness's
demo windows blew up to full page). It is now keyed on `w7-frame`, a hook only
the three real window roots carry.
Verified zoomed in light and dark against 7.css's reference markup: gutter,
dividers, checkmark box, grey right-aligned accelerator all match; bundle
490.92 kB.
Co-Authored-By: Claude <noreply@anthropic.com>
Three defects reported from the real window, each with a distinct cause:
1. "Top and sides do not connect" — 7.css couples the glass treatment to the
title bar with a DIRECT-child selector (.window.glass > .title-bar), and our
.title-bar lived inside an inner div of <app-title-bar>, so the rule never
matched: the title bar fell back to the opaque base style with its own
border and radius, a blue slab sitting on the frame. The .title-bar class
(and the drag region + dblclick-maximize) moved onto the component HOST, so
the selector now matches and the title bar is the translucent streaked glass
fused with the frame.
2. "Black background near the window buttons" — .window.glass::after is
backdrop-filter: blur(4px). CSS cannot sample the DESKTOP behind a
transparent native window (there is no backdrop to read) and WebKitGTK
paints the unsampleable layer BLACK, so every see-through part of the glass
— the ring, the area around the controls — composited over black.
Neutralized in skinfix; the glass tint now shows the real desktop through
the transparent window, which is closer to Aero than a blur we cannot have.
(True blur-behind needs compositor support; GNOME exposes none to
X11/XWayland clients.)
3. "Shadow way too strong" — the 6px gutter hard-clipped 7.css's
2px 2px 10px 1px #000000b3 shadow into a solid dark band. The gutter now
matches the shadow's extents exactly: blur+spread-offset = 9px up/left,
offset+blur+spread = 13px down/right, so it falls off to nothing.
Verified on the REAL window this time: captured the running app with
ImageMagick import (the app runs on XWayland, so X11 capture works) before and
after. Before: black block around min/max/close, murky dark ring, detached blue
title strip. After: streaks span the top, controls hang from the frame edge as
on real Win7, ring is glass-blue, no black anywhere.
Known remaining (pre-existing, next stages): the tab-bar/toolbar row overlaps
itself top-left (tabs conversion, Stage 4); maximized windows should drop
radius/shadow/gutter.
Co-Authored-By: Claude <noreply@anthropic.com>
.window's 6px radius and its outer drop shadow are painted by the webview
OUTSIDE the window's own rectangle, so on an opaque surface they were simply
clipped — square corners, no shadow. chromeOptions now passes
`transparent: true`, the page paints no background of its own, and .window
takes a 6px margin as the gutter the shadow falls into (h-screen shrinks by the
same amount so the gutter does not produce a scrollbar).
Confirmed on Linux/WebKitGTK in a real run, which is the half the Firefox
harness cannot model:
GTK: Created window without decorations (custom titlebar)
GTK: Created transparent window
GTK WebKit: Applied transparent background to webview
Also caught by the run rather than shipped: `transparent` is set in
chromeOptions, which openSecondaryWindow spreads too — so the editor and
connection-manager windows became transparent while still rendering bare
content, i.e. floating widgets on nothing. Both roots now take the same
.window.glass frame and their own windowFocused signal. The editor's blur
binding had to merge with the existing flushDraft() call; a duplicated host key
is a TS1117 build error rather than a merge, which is how it surfaced.
The old anti-flash <style> in index.html is gone with the opaque backgrounds it
guarded — windows are created hidden and revealed on dom-ready anyway.
Not handled yet: a maximized Win7 window has no radius, shadow or gutter. The
6px margin currently applies in every state.
Co-Authored-By: Claude <noreply@anthropic.com>
The app rendered a bare .title-bar with no .window around it, which is why the
Aero frame, glass and drop shadow were missing and the title-bar controls sat
permanently in their inactive colours: EVERY control-glyph rule in 7.css is
keyed on `.title-bar.active` / `.window.active .title-bar`, and nothing ever
set .active.
- app.html's root is now `.window.glass`, so it picks up the 6px radius, the
1px frame, the `inset 0 0 0 1px #fff9` highlight, the drop shadow and the
backdrop-filter glass layer.
- The content below the title bar moved into `.window-body`, 7.css's recessed
inner panel (6px inset + its own border) — the thing that gives a Win7 window
its depth.
- `.active` binds to a new `windowFocused` signal, seeded from
document.hasFocus() and updated from window focus/blur. The webview receives
those when the NATIVE window's focus changes, so no RPC is needed.
Harness renders the shell focused and unfocused: active shows the red close
button and crisp glyphs, inactive desaturates, matching the reference.
Native window transparency (for the drop shadow and rounded corners to fall
outside the window rather than being clipped by an opaque surface) is
deliberately NOT in this commit — it is a native flag with a real chance of
misbehaving on Linux, so it gets its own change and a real-app check.
Co-Authored-By: Claude <noreply@anthropic.com>
7.css gives ONLY tr.highlighted a border (1px solid #aaddfa), and its table is
border-collapse: collapse — so a selected row genuinely grew 1px at top and
bottom and pushed every row beneath it down by 2px. Moving the cursor made the
whole list jitter.
Every tbody row now reserves that 1px as a transparent border, so selecting a
row changes colour and nothing about the geometry.
The rule has to live in `skinfix`, which outranks `skin` by LAYER regardless of
specificity — so a blanket transparent border there also blanks 7.css's own
highlight border. Both selected states therefore restate border-color
explicitly: the inactive pane via --w7-li-bd-hl (the same #aaddfa 7.css uses,
and already restated by the dark palette), the focused pane via --w7-el-bd-h.
Added a "selection geometry" pair to the harness — identical tables, one with a
middle row selected — so the baselines either line up or they visibly do not.
They did not before this change; they do now, verified zoomed.
Co-Authored-By: Claude <noreply@anthropic.com>
The previous model made the dotted rectangle the de-facto focus signal: the
cursor row got ONLY dots (and only in the active pane), so an unfocused pane's
cursor row disappeared entirely, and selection strength was the only band
difference. Wrong way around for a commander UI.
Now (matching Win7 Explorer / the commander convention):
- tr.highlighted = "selected via Space" OR "cursor row" — the template ORs
them, so the cursor row always carries the selection band in BOTH panes.
- Pane focus is what changes the band: strong Aero band (--w7-el-grad-h) in
the focused pane, 7.css's own faint inactive wash in the unfocused pane —
that wash IS Win7's inactive-selection colour, faint by design.
- The dotted rect is demoted to what it was in Win7: a keyboard-focus marker
layered ON TOP of the band, focused pane only. It is no longer the thing
that tells you which pane is active.
Harness now renders a focused and an unfocused pane stacked, with selected
rows and a cursor row in each; verified zoomed in light and dark.
Co-Authored-By: Claude <noreply@anthropic.com>
You were right that selecting a line looked off. Three separate problems, two
of them introduced by the Stage 2 dark-mode block:
1. The selection band was ERASED in dark mode. Stage 2 restated the dark
surface on `table > tbody > tr > *`, i.e. on the CELLS — but 7.css paints
tr.highlighted on the ROW, and a cell background paints over a row
background. The selected row kept only its 1px border and no fill. The base
surface now goes on `table` alone; cells stay transparent so the row band
shows through. Same trap re-applies to the cell separators, which 7.css
deliberately drops inside a selected row and the dark rule was re-adding.
2. The cursor rectangle was drawn PER CELL. `tr.is-cursor > *` puts a dotted
outline around every cell, so the focused row rendered as a row of dotted
boxes. Win7 drew one rectangle around the row — the outline belongs on the
`tr`.
3. Selection was barely visible even in light, and that one is 7.css's own
semantics rather than a bug: listview's `.highlighted` is Win7's *inactive*
selection, a near-white wash. Win7 drew the FOCUSED pane's selection as the
stronger Aero band. The table now carries `is-active` from the panel's own
isActive(), and the focused pane's selection uses --w7-el-grad-h/-bd-h,
which the dark palette already restates, so it recolours for free.
Verified by cropping and zooming the harness screenshots in both themes rather
than eyeballing a full page: full-row band, separators dropped inside it, and a
single dotted rectangle on the cursor row.
Co-Authored-By: Claude <noreply@anthropic.com>
The file list was a CSS grid of <div>s, so 7.css's listview component never
matched a single selector and Tailwind styled the whole thing. It is now the
plain <table> 7.css documents, which is what makes the Aero treatment appear.
- The column header row moved OUT of its own div and INTO <thead>: 7.css makes
thead position:sticky, so it stays put while the body scrolls, and it has to
be inside the same table to do that.
- Column widths moved from a computed grid-template-columns to <colgroup>,
because 7.css sets table-layout: fixed. `gridTemplate()` is deleted.
- Selection is now `tr.highlighted` (7.css's gradient band + #aaddfa border)
instead of bg-primary/15.
- The sort arrow was the text glyph ↑/↓ from `sortIndicator()`, which is also
deleted; the sorted column is now `th.highlighted.indicator[.up]` and 7.css
draws the real clip-path triangle in the header.
- Row height, padding, borders and fonts now come from 7.css, so --row-h and
the per-cell text/color/font utilities are gone.
Two things 7.css does not cover, added in `skinfix`:
- Dark mode. listview.css hardcodes light literals (#fff, #c0c1cd, #eee and a
white header gradient) rather than --w7-* vars, so it does NOT follow the
hand-authored dark palette — the header labels rendered invisible, dark on
dark. The dark arms restate those rules; light keeps 7.css's own values.
- The keyboard cursor. 7.css models selection only; Win7 drew the focused row
with a dotted focus rectangle over it, which is what tells "cursor" apart
from "selected" when they differ. That is `tr.is-cursor`.
Verified visually this time, not just by build log: harness screenshots in both
light and dark show the header gradient, sort triangle, cell separators,
selection band and cursor rectangle matching 7.css's reference markup. Backend
`tsc --noEmit` clean, `bun run build:ui` clean, bundle 489.83 kB (from 490.50).
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>
Everything about this branch's look has so far been verified by reading build
logs, which is how a fully black window got reported as a clean boot. This adds
a way to actually LOOK at the skin.
- scripts/shot.ts — renders a page with Firefox headless (already installed; no
Chromium/puppeteer download) and writes a PNG. Firefox needs a dedicated
profile, since a running Firefox locks the default one.
- ui/dev/harness.html — static gallery pairing 7.css's own documented markup
("reference") against what the app currently renders ("ours"), for the window
frame, ListView, context menu, buttons, textbox, progress bars and tabs. Lives
outside ui/src so the Angular build never sees it.
Two traps found while building it, both documented in the files:
- The composed page MUST be written into ui/dist beside the stylesheet.
Firefox's security.fileuri.strict_origin_policy blocks a file:// document
from loading CSS in another directory, and the result renders completely
unstyled — indistinguishable from "the skin is broken".
- replaceAll, not replace, for the {{STYLES}} token: the harness documents the
token in a header comment that precedes the <link>, so a first-match replace
substitutes the comment and leaves the stylesheet unlinked. This produced one
false "everything is unstyled" result before it was spotted.
What the first screenshots already confirm, visually:
- The window has no Aero frame, radius, drop shadow or glass, and its title-bar
controls sit in their inactive colours — we render a bare .title-bar with no
.window.glass.active wrapper.
- The file list gets none of the ListView treatment (header gradient, sort
indicator, cell separators, selection band) because it is a div grid.
- The context menu gets none of the menu treatment (gutter, dividers, disabled
state, real checkmarks).
- NEW, not previously predicted: `.win7 button { min-width:0; padding:
revert-layer }` — added to stop Tailwind icon buttons ballooning — also
crushes legitimate dialog push buttons well below Win7's 75x23 metrics.
- Dark mode: ListView header labels and tab labels are invisible (dark on
dark), since listview.css hardcodes light literals the dark block never
restates.
Caveat recorded in the script: Firefox is Gecko, not the WebKitGTK engine the
app ships on. Good for skin fidelity, useless for native window transparency.
Co-Authored-By: Claude <noreply@anthropic.com>
main retired electrobun's native context menu (a documented no-op on Linux) in
favour of an HTML one on every platform. Merging that here needs the menu to
look right under the Win7 skin as well.
Conflicts, all resolved by keeping both sides:
- src/bun/index.ts — createAppRPC keeps the branch's mainWindow getter, drops
main's removed contextMenu dep.
- ui/src/app/app.ts — imports both TitleBar and ContextMenu.
- ui/src/app/platform.ts — kept the branch's version (its IS_MAC_CHROME accounts
for the frameless win7 chrome, and MOD_LABEL correctly keys on the real
platform rather than the chrome), plus exporting IS_MAC and adding IS_LINUX
for the menu's per-OS shell wording.
Aero menu (branch-only): 7.css has no menu component, so it is hand-authored
across both layers — the row <button>s shed their push-button slab in `skin`
(revert-layer only reaches `utilities` from there), and the square-cornered
slab, hairline border and gradient selection band are stated in `skinfix`.
Built strictly from existing --w7-* vars, never literal hex, so the
hand-written dark Win7 palette recolours it for free. Hover and the keyboard
cursor share one band (the component marks the cursor with `bg-accent`, which
is overridden alongside :hover) as a real Win7 menu does.
Also answers a question the merge raised: frameless windows DO work on Linux.
The GTK wrapper honours titleBarStyle "hidden" — it calls
gtk_window_set_decorated(false) and logs "GTK: Created window without
decorations (custom titlebar)". Verified on GNOME/Wayland and documented in
architecture.md.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (492.52 kB
initial, still under the 500 kB budget), app launches with the skin active,
frameless, no console errors and no GBM failures.
Co-Authored-By: Claude <noreply@anthropic.com>
On the NVIDIA proprietary driver the app booted, logged "miniSCP started",
reported no error — and painted a completely black window, with stderr showing
"Failed to create GBM buffer of size WxH: Invalid argument" and an X11
GLXBadWindow. WebKitGTK's DMABUF renderer cannot allocate buffers there;
WEBKIT_DISABLE_DMABUF_RENDERER=1 is the upstream workaround. Pre-existing on
Linux, unrelated to the context-menu change — it just had never been looked at
because nothing on Linux had been run before.
The app CANNOT apply this to itself. WebKit reads the variable when it forks
its web process, and Bun's `process.env` writes do not propagate to child
processes (verified: a spawned child sees it unset), so setting it in
src/bun/index.ts is inert even from a module imported before `electrobun/bun`.
It has to be in the environment before the app process starts, which means
every launch path must inject it:
- scripts/linux-gpu.ts — shared `launchEnv()` helper, gated on
/proc/driver/nvidia so Intel/AMD keep hardware compositing, and never
overriding an explicit value from the user's environment.
- scripts/dev.ts — uses it for `bun run dev`.
- scripts/start.ts (new) — `bun run start` previously called `bunx electrobun
dev` straight from package.json, where no env could be set; it now goes
through a script like dev does.
- scripts/release.ts — writes a `miniSCP.sh` wrapper beside bin/launcher in the
Linux tarball, since a packaged app goes through none of the above.
Documented in gotchas.md (including that a "does it boot?" log check does NOT
catch this — the log looks healthy) and platform-notes.md, with the smoke test
updated to require that the UI actually renders.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean, and
`bun run start` now boots with zero GBM/GLX errors.
Co-Authored-By: Claude <noreply@anthropic.com>
`bun run start` died on Linux with ERR_DLOPEN_FAILED (missing
libayatana-appindicator3.so.1). Fixing the environment exposed the real gap:
electrobun's ContextMenu.showContextMenu is a documented no-op on Linux, and
since the UI suppresses the webview's own right-click menu, EVERY panel context
menu was silently dead there — Send to Other Panel, Open, Rename, Permissions,
Delete, Copy Path, Reveal and the column picker all unreachable. Electrobun
itself logs "Application menus are not supported on Linux. Implement menu UI in
your webview HTML instead." at boot.
- Context menus are now HTML on EVERY platform (ui/src/app/context-menu.ts +
context-menu.service.ts), not just as a Linux fallback: one implementation
cannot drift from a second one. The item builders are ported verbatim from
the old bun menu, so ordering, per-platform labels and the Windows-local
chmod hide are unchanged, and picks are emitted as the same `ctx-*` / `col:*`
strings through RpcService.emitMenuAction — App.handleMenuAction is untouched
and still the single dispatcher for both the macOS menubar and right-clicks.
Adds what the native menu gave for free: arrow/Enter/Escape keys, viewport
edge-flip, close on blur/resize/scroll. The Edit item now shows the real F4
hint instead of the native menu's "e" mnemonic.
- New shellItemAction RPC replaces showRowMenu/showColumnsMenu — the only part
the webview can't do itself (reveal, open-with-default, copy path). Deletes
src/bun/context-menu.ts entirely.
- release.ts gains a linux-x64 target. Electrobun emits only the .tar.zst
updater blob on Linux, so the artifact is a .tar.gz of the assembled app
folder, mirroring the Windows portable zip.
- Docs: platform-notes gets a Linux runtime-dependency table (AppIndicator is
linked HARD — a missing lib is a boot failure, not a degraded tray), the
ldd diagnostic, GNOME tray caveat, known issues and a first-boot smoke test;
gotchas gets the ERR_DLOPEN_FAILED trail and the benign GLX/GBM startup
noise; architecture/build-and-release/README updated accordingly.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (399 kB
initial), app boots on Linux with no errors beyond the documented benign
warnings. NOT verified on macOS/Windows — the native menu is retired on those
platforms too, so the menus need eyeballing on a Mac before the next release.
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 darker tone sat at #090908 for --background while --chrome was already
pure black, which both inverted the elevation ramp (frame strips darker than
the content beside them) and missed the point of an AMOLED tone — only a
true #000000 pixel is actually off on OLED.
--background now collapses into --chrome at pure black, so the main window,
connection manager and editor all render black end to end: panes, bars,
sidebar, gutter and textarea. Elevation is kept where it earns its place —
dialogs, popovers and toasts stay #181817, hover and selection #272726 — and
--border/--input go from 12%/15% to 16%/18% white, since on pure black those
lines are the only thing left defining panes, bars and rows.
No plumbing needed: buildPreload already bakes data-dark-tone onto every
window pre-paint, and tone changes broadcast live to open windows.
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>
The main feature (copy selection to the other panel, F5) is now labeled
"Send" in the toolbar button, tooltips, and context menu across all 7
locales. Internal identifiers (copyToOtherPanel, ctx-copy, i18n keys)
and secondary strings (toasts, status bar, errors) are unchanged.
The context-menu entry moves ahead of Open/Edit as the first row item.
README, marketing site, and the Windows QA checklist updated to match.
Co-Authored-By: Claude <noreply@anthropic.com>
~200 MB macOS / ~300 MB Windows is normal for a Bun + system-WebView app;
record the process breakdown, why Windows (WebView2/Chromium) is higher,
that it's not a bug, and the only real growth levers, so it isn't
re-investigated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Quit item was role:"quit" with no accelerator, so ⌘Q was never
bound. Make it a custom action + accelerator "q" dispatched to
Utils.quit(), which emits before-quit so the existing guard (active
transfers / unsaved editors, native confirm dialog) runs instead of the
OS terminating directly. Handled before the manager-open check so ⌘Q
works with the connection manager open too.
Co-Authored-By: Claude Fable 5 <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>
Self-contained, no-JS landing page (tinygrad-minimal) that doubles as the
artifact download host, served by Caddy on an internal :80 box.
- site/index.html: static page, "Same tool, only a little different", feature
highlights (remote->remote, symmetric panels, ssh_config awareness, edit-in-
place, self-copy guard, ...), fixed stable download links, FAQ (unsigned notes).
- site/Caddyfile: one flat /dl/ folder with browse + range requests and per-type
Cache-Control (patches immutable, update.json no-cache, installers short).
- site/deploy.sh: drop a build's artifacts/stable-* into /dl/ (renames the win
portable zip to a stable name).
- Flat folder matches electrobun's artifacts/ layout, so it's also the future
updater baseUrl; per-build folders/symlink dropped in favor of it.
- docs/build-and-release.md: document the download host + deploy flow.
Verified: caddy validate + live smoke test (links, cache headers, 206 range,
404 fallback, deploy.sh copy/rename) against a temp root.
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>
Codify three standing behaviors so they persist across sessions: ask
clarifying questions in plan mode until unambiguous; verify then
auto-commit + push after an approved plan; keep docs/ up to date in the
same change.
Co-Authored-By: Claude <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>
Three layers: windows are created hidden and revealed on the webview's
first dom-ready (800ms fallback); the bun preload bakes the persisted
theme data-attributes so the first paint is already themed; and
index.html carries an inline background style covering the
pre-stylesheet paint (values mirrored from styles.css tokens).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bun run release -- --target mac-arm64|mac-x64|win-x64: gen version/icons,
build UI, electrobun build --env=stable, collect artifacts as
release/<target>/miniSCP-Build<n>-<target>.*. mac-x64 builds on the
arm64 Mac by running electrobun's x64 CLI under Rosetta 2 (verified:
x86_64 launcher in the produced app). Windows also zips a portable
build next to the Setup installer. Tag-triggered Gitea Actions matrix
included, inert until runners exist. legacyNames emptied — the BunSCP
migration has served its purpose.
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>
Reload/Inspect Element no longer appear on uncontrolled right-clicks
in any window; text fields keep the system text menu.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native checkable menu on the column headers toggles Size, Modified,
Kind, Rights, Owner and Group (persisted, both panels). Owner/group
come from the ls-style SFTP longname remotely and cached uid/gid
lookups locally (POSIX; undefined on Windows). All columns sort.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local left, remote right — the commander convention. Panel-dropdown
connects still target the panel they were opened from.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Import moves to the window header; sidebar widens with a muted tint;
the stack row's count and action buttons swap on hover instead of
sharing the row, so long names can't collide with them.
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>
Dialogs only needed refocus(); injecting PanelsService boots a whole
workspace, which secondary windows must not do.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Real BrowserWindow per edited file with its own RPC instance, deduped by
sessionId+path. The same Angular bundle boots an editor-only root via
preload globals. Native close isn't cancellable in electrobun, so the
editor mirrors its draft to bun; traffic-light closes get a native
Save/Discard prompt after the fact, and failed saves park the draft in
the edit-leftovers dir. In-window closes (Cmd+W, Close) use a styled
prompt; the quit guard now also counts unsaved editors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Folders become Stacks (connections.json v2, no v1 migration): collapsible
stack-first list, open-stack-in-tabs (first reuses a pristine tab), drag
connections between stacks. More... reveals a per-connection Local
directory applied to the other panel when local and untouched. Import
reads a WinSCP.ini export incl. deobfuscated passwords.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>