Add Gitea package registry support
Add publish-dmg target for versioned DMG uploads
Update deploy-dmg to include Gitea publishing
Document Gitea token requirements in README
Wires the existing closeSurfaceCmd into the panel header (red-on-hover X next
to zoom) and adds a Close button to the stopped overlay, so a panel — including
an empty/stopped one — can be dismissed instead of resumed/restarted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses final-review findings: Cmd::Shutdown now snapshots all live surfaces
synchronously before exit (spec graceful-shutdown requirement); StoppedSnapshot
calls detachSurface on unmount to release the bridge output channel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update UI to use CloudDownload icon instead of RefreshCw
Improve error handling for update checks
Add better visual feedback for update states
Change GitHub link to RealManual repository
Matches the clear-all trash icon; dimmed/disabled when there are no unread events.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The handshake ran synchronously in Bridge::connect: on a build-id mismatch it
sent Cmd::Shutdown and awaited a reply that never flushes (the daemon exits
first), so request() hit its 5s timeout and the reconnect-retry respawned the
daemon and re-sent Shutdown — a loop that produced repeated 'spaceshd
listening' lines and a multi-second launch delay. The id stamps also differed
between the separately-built daemon and GUI, so it fired on normal launches.
- Remove the handshake auto-restart; `make install`/`reinstall` already kill
and replace the daemon reliably. health.build stays for display in Settings.
- Shutdown now goes through a fire-and-forget send (no reply wait, no retry),
fixing the same loop for the Settings Restart button.
- Makefile: `make app-bundle` builds just the .app via `tauri build --bundles
app` (no .dmg, no hdiutil) and `reinstall` uses it — faster self-update that
can't hang on a mounted DMG volume.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Toggling the sidebar off used to hide it entirely, losing the per-workspace
status rings. It now collapses to a 48px rail showing each workspace's
aggregate status ring (and unread dot), still clickable to switch, plus the
new-workspace button and the daemon live/offline dot. Full sidebar returns
when toggled back on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds Cmd::ClearEvents + Evt::EventsCleared: the daemon drops the persistent
event log (keeping next_id monotonic), persists, and broadcasts so every
client empties its list. A red trash icon next to 'Mark all read' triggers it;
disabled when the list is empty. Threaded through proto, the daemon handler,
the Tauri bridge, and socketBridge. Includes an EventLog::clear test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pointer-events:none lets clicks/hover pass through the unread badge to the
bell button beneath, which the badge was previously swallowing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The daemon outlives the GUI, so after an update an OLD daemon can keep serving
the socket and the new GUI just connects to it (stale code — e.g. the missing
TERM fix). Both binaries are now stamped with the git build id (build.rs):
the daemon reports it in `health.build`, and on connect the bridge compares it
to the GUI's own SPACESH_BUILD; on mismatch it shuts the daemon down and lets
ensure_daemon respawn the bundled (matching) one. No-op for unstamped dev
builds or daemons too old to report a build. Build id is shown in Settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reconnect() spawned a new reader/writer but left the previous reader task
running. A reconnect triggered while the old connection was still alive (e.g.
a request timing out during a slow daemon start) left TWO live connections;
the daemon broadcast Output to both, so every byte — including input echo —
arrived twice ("ccucurcurl"). The bridge now stores the reader's JoinHandle
and aborts it before establishing the new connection, guaranteeing a single
live reader.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 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>
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>
- 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>
- 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>
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>
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>
Source Event Center from daemon event_log (seed + live event/events_read push).
Unread/Errors tabs filter real EventRecord flags; bell shows numeric unread badge;
clicking an entry calls focusSurface + markEventsRead(ids). notify.ts param widened
to string so exit kind type-checks without breaking existing NOTIFY_STATES guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The app is its own cargo workspace, so in 'tauri dev' the app binary lives
in app/src-tauri/target/ and spaceshd is NOT a sibling — lazy-start failed
and the .expect() crashed the window. Now: find_daemon tries SPACESHD_BIN,
sibling, repo-root target/{debug,release}, then PATH; bridge honors
SPACESH_SOCK like the daemon/CLI; setup logs instead of panicking.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>