Double-click a sidebar workspace name to edit it inline; Enter/blur commits
via setWorkspaceMeta({name}) (empty/unchanged is a no-op), Esc cancels. The
input stops pointer/key propagation so it doesn't trigger select or drag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tauri's universal target builds each arch separately and resolves externalBin
with that arch's triple (spaceshd-aarch64-apple-darwin / -x86_64-apple-darwin),
lipo'ing them itself. The previous single -universal-apple-darwin sidecar made
the per-arch sub-build fail with 'resource path ... doesn't exist'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tauri build bundled only the GUI binary, so the packaged app had no daemon:
find_daemon() looks for a sibling `spaceshd` next to the GUI
(Contents/MacOS/spaceshd) and ensure_daemon failed to spawn it → "offline".
(Dev worked only because find_daemon falls back to the repo-root target path.)
- tauri.bundle.conf.json: a build-only overlay adding bundle.externalBin
["bin/spaceshd"], kept out of tauri.conf.json so `tauri dev` doesn't require
a sidecar file.
- Makefile: `make dmg` now builds spaceshd for both arches, lipo's a universal
sidecar into src-tauri/bin/spaceshd-universal-apple-darwin, and passes
--config so it lands in Contents/MacOS/spaceshd. `make dmg-native` does the
host-arch equivalent.
- .gitignore: ignore the generated app/src-tauri/bin/.
After install, the unsigned helper runs once quarantine is cleared recursively:
xattr -dr com.apple.quarantine /Applications/spacesh.app
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No self-hosted macOS runner exists and Tauri can't cross-compile a macOS
bundle on Linux, so the DMG is produced locally with `make dmg`. The Gitea
workflow is now landing-only (build & push the nginx image + Max notify);
removed the dmg job, the changes/paths-filter job, and the app/crates triggers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No macOS CI runner available, so the universal .dmg is built locally:
`make dmg` (adds the rust targets + tauri build --target universal-apple-darwin).
Also: deps, dmg-native, dev, daemon, test, landing-image/run/push, clean.
`make` / `make help` lists targets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- landing/: the spaceshell.ru terminal-dark landing (index.html + screenshots),
containerized as an nginx:alpine image (Dockerfile + nginx.conf with gzip and
asset caching, VERSION, .dockerignore).
- .gitea/workflows/build.yaml: adapted from the coddykinder pipeline to this repo.
Path-gated jobs — `landing` builds & pushes the nginx image to the Gitea
registry on landing changes; `dmg` builds a universal (Intel + Apple Silicon)
.dmg via `tauri build` on app/crates changes and uploads it as an artifact;
Max notification summarizes both. Tags build everything (release).
- DOCS/landing/spaceshell-landing.md: build brief + copy + SEO meta.
Notes: the DMG job needs a self-hosted macOS runner labelled `macos` (Tauri
can't cross-compile macOS from Linux); the DMG is unsigned until Developer ID
secrets are wired. Landing image verified locally (HTTP 200, assets served).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Tauri bridge connected to the daemon once at startup and held a single
stream with no recovery: when the daemon exited (Restart/Stop, crash, or an
update), the reader emitted spacesh:disconnected and died, and every later
request went through the dead writer forever — the GUI was permanently stuck
(settings frozen, offline). Since the bridge is Rust-side state that survives
a webview reload, even Cmd+R didn't recover it.
- bridge.rs: requests now reconnect-and-retry on failure with a single-flight
guard (generation counter) so concurrent failures collapse into one
reconnect and never open duplicate connections; a 5s reply timeout catches
silently-dropped connections. ensure_daemon respawns the daemon if it
exited. On success the bridge emits spacesh:reconnected.
- App.tsx: on spacesh:reconnected, bump a connection epoch that keys
LayoutEngine, remounting terminals so they re-attach (snapshot + live stream)
to the restarted daemon; also reload health/config/status.
- Settings: drop the Stop button — with lazy daemon spawn any GUI request
resurrects the daemon, so an in-GUI "stop" is contradictory. Restart now
works end to end (shutdown → reconnect respawns → panels re-attach).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Font change now applies to already-open terminals: the WebGL renderer
caches glyphs in a texture atlas keyed by the old font/size, so the live
re-apply effect now calls webglAddon.clearTextureAtlas() (via a new ref)
after updating fontFamily/fontSize, before refitting.
- Daemon uptime now reflects a restart: the Settings daemon section ticks
every second for live uptime, and Stop/Restart trigger an onReload callback
that re-fetches health/status in App so a restarted daemon's new
started_at_ms is shown instead of the stale value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Config in config.toml via daemon (Get/Set/ConfigChanged), low-churn CSS-var
theme switching (Dark/Light + accent), terminal font, default shell, and
daemon status with Stop/Restart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FAVORITES section at the top collects pinned workspaces (removed from their
group listing); a star toggle on each row pins/unpins via setWorkspaceMeta.
- Drag-to-reorder within a section using raw pointer events (HTML5 DnD is
unreliable in the macOS WKWebView), with a drop-line indicator; on drop the
section's `order` is reassigned sequentially and persisted. Cross-section
drops are ignored (group membership unchanged).
- Trash icon on row hover opens a ConfirmDelete modal that shows the live
terminal count and warns before terminating them, then calls close_workspace
and re-points the active workspace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `pinned` bool to the workspace model, threaded through proto
(Workspace + WorkspaceView + SetWorkspaceMeta), the registry, the
set_workspace_meta handler, persistence, the CLI mapping, and the Tauri
bridge. serde(default) keeps existing state.json compatible (pinned=false).
Backs the sidebar Favorites section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Search fixes:
- TerminalView sets allowProposedApi (the search addon's match decorations
use registerMarker/registerDecoration); without it findNext threw before
firing results, so the counter was stuck at 0/0.
- The search bar now renders inside the panel it targets (in the header)
instead of a global top-right overlay, so it's obvious which panel is
searched.
- Search is anchored to the panel it was opened on (searchSurfaceId) and no
longer follows focus — opening it in one panel and switching away no longer
shows it open elsewhere.
Prompt duplication:
- The focus border was 1px when unfocused, 2px when focused; with border-box
that resized the content on every focus switch, firing ResizeObserver -> fit
-> PTY SIGWINCH and making zsh/powerlevel10k reprint its prompt. The border
is now a constant 2px, color-only on focus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A new surface spawned its shell immediately at the default 80x24, so the
shell (e.g. zsh + powerlevel10k) printed its first prompt before the GUI
fit-resized the panel. The post-print resize reflowed the grid, leaving the
prompt invisible until Enter and shifted by a line.
The surface actor now defers the child spawn: it waits for the first Resize
(spawning at that geometry) or, for headless/CLI surfaces that never attach a
GUI, falls back to the spec geometry after 250ms. Attaches received before
the spawn get an empty-grid snapshot plus a live subscription; pre-spawn
input is buffered and replayed. Adds two deterministic tests via `stty size`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- LayoutEngine: fix splitter resize (track pointer 1:1 via delta-from-start)
and add panel drag-to-reorder using raw pointer events with drop indicators
- TerminalView: auto-fit xterm to container via FitAddon + ResizeObserver
- App/TopBar: toggleable sidebar; persist sidebar/events collapse in
localStorage; bell icon opens the activity log
- Wizard: new-workspace modal now grabs focus and handles keyboard
- deps: add @xterm/addon-fit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a config.rs module that loads optional ~/.spacesh/config.toml and a
default_shell() resolver: SPACESH_SHELL env -> config.toml default_shell ->
login shell from the passwd DB -> $SHELL -> /bin/sh. The passwd lookup
matters under launchd where $SHELL is absent. All plain-panel spawn sites in
server.rs now use the resolver. Adds toml + libc deps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dock showed a black square because tauri.conf.json had no bundle.icon
block and icons/ held only tiny placeholders with no .icns. Add an SVG
source logo (terminal prompt + split workspace panes), generate the full
icon set via `tauri icon` (icon.icns/.ico + all PNG/iOS/Android sizes), and
wire bundle.icon so the daemon embeds and macOS renders it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Search only ran on Enter; typing merely reset the counter to 0/0, so a
visible term showed no matches until the user pressed Enter. run() now takes
an optional query override and onChange fires it on every keystroke for
search-as-you-type, while Enter/Shift+Enter still navigate matches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lock_is_exclusive_within_process acquired the global ~/.spacesh/daemon.lock,
so it flaked whenever a real daemon was running. Add a SPACESH_LOCK env
override to lock_path() and point the test at a private temp file under a
serial() guard, making the suite deterministic regardless of a live daemon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire Cmd::SetZoom through Tauri bridge (set_zoom command), add zoomed
field to WorkspaceView, short-circuit LayoutEngine to render only the
zoomed panel full-grid, and toggle Maximize2/Minimize2 in panel header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>