Skip to content

Shared Domain Types and File URL Helpers

dazeb edited this page Sep 17, 2026 · 2 revisions

Shared Domain Types and File/URL Helpers

src/shared is the cross-process contract layer for Termsprawl. It holds the data shapes and IPC channel names that Electron main, preload, renderer, core, and the Server Edition all agree on. The directory rule is explicit: it must stay Electron-free because it is imported by both core and renderer, and channel strings must never be hardcoded outside ipc.ts.

Module map

File Responsibility
src/shared/ipc.ts Single source of truth for IPC channel strings: workspace, pty, files, agent hooks, context links, managed accounts.
src/shared/types.ts Shared domain types: node kinds, workspace shapes, context link pairs, agent accounts, app settings, cloud/chat/browser/file contracts.
src/shared/file-url.ts termsprawl-file://local/... preview URL helpers.
src/shared/agent-status.ts Agent status and notification types shared with the hook server.
src/shared/agents/config.ts Agent registry/preset config for Claude, Codex, Gemini and Grok.
src/shared/remote-project.ts Remote project-related shared types/helpers.
src/shared/node-links.ts Shared link data shapes used by the link engine and UI.
src/shared/agent-tools.ts Shared agent-tool protocol types.
src/shared/update-status.ts Shared update-status types.
src/shared/*.test.ts Contract tests for file URLs, settings contracts, and update status.

The directory also contains the shared contract tests file-url.test.ts, settings-contracts.test.ts, and update-status.test.ts; file-url.test.ts explicitly guards the protocol round-trip and rejection behavior.

Boundary architecture

flowchart LR
  EM[Electron main] --> SH[src/shared]
  PR[Preload bridge] --> SH
  RN[Renderer] --> SH
  SRV[Server Edition] --> SH
  CORE[core services] --> SH
  SH --> IPC[ipc.ts<br/>channel strings]
  SH --> T[types.ts<br/>domain contracts]
  SH --> FU[file-url.ts<br/>preview URL protocol]
  SH --> AS[agent-status.ts]
  SH --> AC[agents/config.ts]
Loading

The key architectural point is that src/shared is not a service layer. It is the vocabulary boundary. Main/Server Edition perform the actual work; the renderer and core consume the same shapes; ipc.ts keeps channel names enumerable; types.ts keeps request/result/state contracts structural. Because this layer cannot import Electron, it also cannot depend on runtime host capabilities.

File/URL helpers

file-url.ts defines the custom protocol used to preview local images without exposing a file:// hole to the renderer:

  • Protocol constant: FILE_PROTOCOL = 'termsprawl-file'.
  • Shape: termsprawl-file://local/<absolute/path>.
  • The dummy host local is required so Chromium treats it as a standard URL.
  • toFilePreviewUrl(absPath) splits the path on /, encodes each segment with encodeURIComponent, joins them, and prefixes ${FILE_PROTOCOL}://local.
  • fromFilePreviewUrl(url) parses the URL, requires the termsprawl-file: protocol and hostname local, decodes the pathname, and requires the result to start with /.
  • It returns null for foreign schemes, non-local hosts, relative/empty payloads, invalid URLs, or any parse error.
flowchart LR
  A[absolute filesystem path] --> B[toFilePreviewUrl]
  B --> C[termsprawl-file://local/&lt;encoded path&gt;]
  C --> D[fromFilePreviewUrl]
  D --> E{protocol = termsprawl-file:?<br/>host = local?<br/>path starts with /?}
  E -- yes --> F[absolute path]
  E -- no --> G[null]
Loading

file-url.test.ts verifies the round trip, encoding of spaces and unicode, rejection of foreign schemes such as https:// and file:///, and rejection of relative or empty payloads. The helper assumes callers pass an absolute path; the decode side is the validating side.

Terminals, PTYs and remote projects

PtyCreateRequest is the shared terminal-spawn contract:

  • id is stable per-node and also the tmux session key.
  • projectId lets main clean up live sessions before persistence settles.
  • shell, cwd, command, cols, rows, env, terminalProfile, and httpProxy describe the local or remote process.
  • command runs as shell -lc <command> so the user’s login PATH applies.
  • remote switches the terminal from local node-pty to ssh -tt plus remote tmux.

ProjectRemote carries user?, host, port?, and path. GitTarget models where git/file operations run: a local project sends cwd, while a remote project sends remote and cwd is null. The renderer is expected to set exactly one side; main validates both against the known project list.

Terminal lifecycle result types include PtyCreateResult with pid and fresh (false means warm tmux reattach, true means cold start), PtyExitInfo with exitCode and optional signal, and DurableCleanupResult for destructive metadata operations where committed sessions still need retry cleanup.

Workspace, project and node persistence

SerializedNode is the persisted node shape and intentionally mirrors core’s SerializedNode so the renderer never imports core. It carries:

  • id, type, position
  • parentId for group/frame membership, kept out of live React Flow state but persisted
  • width, height, and style so resized nodes restore their saved size instead of reverting to content-measured size
  • data for node-specific state

ProjectMeta stores id, name, cwd, optional remote, closed, optional archived, and optional per-project settings. ProjectSettings currently includes accent, used for node accent dots.

WorkspaceSnapshot is the persisted workspace envelope:

  • index.version: 1
  • index.projects: ProjectMeta[]
  • optional pendingTerminalCleanup and pendingTerminalNodeCleanup
  • optional terminalTombstones
  • projects: Record<string, SerializedNode[]>

Those cleanup and tombstone fields are the shared representation of terminal deletion/cleanup state across persistence boundaries.

Settings and agent capability model

AppSettings is the central user-configuration shape. It covers update downloads, managed agent accounts, announcement dismissal, cloud API origin, A2A peers, API provider endpoints, theme, enter behavior, browser agent control, A2A server enablement, wheel zoom inversion, onboarding timestamp, browser home URL, terminal font/profile/proxy settings, Telegram, chat, and relay configuration.

Several settings are intentionally local-machine-only or secret-adjacent:

  • TelegramSettings stores the user’s bot token and allowed chat ids.
  • ProviderKey stores provider API keys in settings.json only; env overrides are documented as TERMSPRAWL_PROVIDER_KEY_<ID>.
  • A2APeer.token may hold a bearer token, with env override TERMSPRAWL_A2A_PEER_TOKEN_<ID>.
  • relay.trustedFingerprint persists a confirmed peer identity after fingerprint confirmation.

Agent capability types describe CLI-side extensions:

  • SettingsSkill: enabled is real state; disabled skills live in skills-disabled/, and toggling moves directories.
  • SettingsHook: distinguishes managed hooks installed by Termsprawl from legacy hooks owned by another tool.
  • SettingsCommand: built-in, project, or agent.
  • SettingsMcpServer: read-only discovery of stdio or http servers; the CLI launches them.
  • SettingsPlugin: CLI cached plugin with enabled, skills, and agents counts.
  • SettingsSubagent: reusable CLI-loaded subagent definition.
  • SettingsCapabilities: aggregate wrapper with supported, optional reason, and arrays for each capability family.
  • UsageStats: usage totals, sessions, daily breakdown, and model breakdowns.

AgentAccount includes id, label, agentId: 'claude', a configDir under userData/accounts/<id>, and a per-account permission mode. The type comment explicitly says never store tokens there.

Browser, file, diff, chat, cloud and bundle contracts

BrowserCdpInfo describes the localhost-only CDP facade: port, per-boot bearer token, wsUrl, and host (127.0.0.1). The token is required on every CDP call. BrowserNavigateResult is a small discriminated result with ok: true or ok: false plus UNKNOWN_NODE | DENIED.

File and diff node contracts include:

  • DiffBase = 'staged' | 'HEAD'
  • DiffInfoResult with original, modified, and optional error code NO_REPO | MISSING | IO
  • FileErrorCode = 'MISSING' | 'IO' | 'UNSUPPORTED' | 'OUTSIDE'
  • FileReadResult variants for text, markdown, image, and error
  • FileWriteResult for success or error

ChatNodeData persists a chat node’s provider/model/system/messages, including tool calls, usage, cost, and a cache-only streaming flag. ChatSettings stores default provider/model, provider keys, and optional price overrides for the cost chip.

Cloud contracts mirror the web API and include plans (free, pro, canvas), backup status, sync state, user, backup/detail, sync status, device-flow start/poll, online spaces, space access, space pull/push results, and GitHub repo/import results. CloudSpacePullResult carries imported SerializedNode[] so the renderer can hydrate them through its own deserialize path. CloudGithubRepo.cloneUrl is present on the wire but stripped before anything crosses IPC. Workspace bundle export/import contracts report saved/imported project counts and the first project id to switch to.

Announcements are pure shared data: Announcement has version, title, and body.

Call chains

  • Terminal creation: renderer builds PtyCreateRequest → IPC channel from ipc.ts → main resolves local vs remote project → returns PtyCreateResult → later emits PtyExitInfo.
  • Project deletion/cleanup: destructive metadata operations return DurableCleanupResult, whose cleanupPendingIds feed pending terminal cleanup state across persistence.
  • Workspace reopen/switch: persisted SerializedNode[] inside WorkspaceSnapshot are deserialized by the renderer, restoring position, size, style, parent frame, and node data.
  • Cloud space pull: main pulls a space, imports it as a new local project, returns CloudSpacePullResult containing SerializedNode[], and the renderer hydrates through its normal path.
  • File preview: renderer calls toFilePreviewUrl(absPath) → emits termsprawl-file://local/... → main maps it back with fromFilePreviewUrl(url).
  • Settings/capability UI: shared shapes from types.ts are populated by settings/capability readers and consumed by the settings panel and capability pages.

Boundaries and edge conditions

  • src/shared must remain Electron-free. This is why SerializedNode mirrors core’s type and ChatNodeData stays structurally in sync instead of importing core.
  • IPC channel names are centralized. Adding a capability should start in ipc.ts, not in a handler or component.
  • Node kinds are a shared union. The documented rule is: when adding a node kind, extend the union in src/shared and in src/renderer/src/state/workspace.ts.
  • fromFilePreviewUrl is the guard for preview URLs: foreign schemes, non-local hosts, relative paths, empty payloads, and parse failures all return null.
  • Secrets are typed into settings only, not into project files or IPC payloads. Provider keys, Telegram tokens, relay fingerprints, A2A peer tokens, and agent account directories are explicitly local-only or never-token-bearing.
  • Remote/local targeting is mutually exclusive for git/file ops: GitTarget expects one side set, and main validates against known projects.
  • Some contracts carry incomplete/partial provider data, e.g. UsageStats model breakdowns and AgentAccount.permissionMode; consumers should treat optional fields as optional rather than inventing defaults in shared code.

Extension points

  • Add or rename an IPC channel in src/shared/ipc.ts.
  • Add a node kind to the shared union and the renderer workspace state.
  • Add request/result/state shapes in src/shared/types.ts, keeping them structural and Electron-free.
  • Add file preview protocol behavior only alongside file-url.test.ts round-trip and rejection coverage.
  • Add settings or capability fields in the shared types first, then thread them through the settings store, IPC surface, and UI.
  • Keep contract tests such as settings-contracts.test.ts and update-status.test.ts in sync with shared changes.

Sources: src/shared/AGENTS.md, src/shared/file-url.ts, src/shared/file-url.test.ts, src/shared/types.ts

termsprawl

App Shell & Platform Foundations

Canvas, Nodes & Renderer State

Terminals & Session Continuity

Persistence, Projects & Files

Agent Runtime & Tooling

Chat Nodes & Model Providers

Git & Source Control

Embedded Browser Nodes

Server Edition

Relay & Remote Access

Integrations & Secondary Surfaces

Settings, Updates & Maintenance

Clone this wiki locally