Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,46 @@
Human-curated release notes. Earlier 0.9.x notes also live on the
[GitHub releases](https://github.com/benjsmith/switchbay/releases) page.

## 2026-10-08 — v0.14.0

**Migration:** none. Your settings carry over. **Breaking:** the Graph tab is
now always Curiosity Engine's own viewer; Switch Bay's separate copy of it is
gone.

Works with curiosity-engine 1.9.2, curiosity-merge 0.8.4, and okstratr 0.2.0.

### Changed

- **Graph is Curiosity Engine's viewer** — the Graph tab, the wiki page list
in the left column, and the graph in Zen all show Curiosity Engine's own
viewer. Search, type filters, Atlas and Classic views, keyboard shortcuts,
the page window, note editing, curation replay and splitting a workspace
all come from Curiosity Engine, so they match what you see in its own
viewer and pick up its improvements when you update it.
- **Switch Bay extras inside the CE viewer** — pages still render maths,
tables still open as a Sheet or Plot, pages can still start a slideshow,
Procedures and Executions have their own label colours, and the + button
still opens Switch Bay's upload dialog.
- **Without Curiosity Engine** — the Graph tab is hidden and links that would
open it go to the Editor instead. Files, uploading to the vault, RAG,
Agents and Library work as before. A workspace with no wiki yet shows a
short note in the Graph tab instead of an empty canvas.
- **Settings → "Agents: okstratr observer"** — the old "Proxied skill
embeds" switch now only chooses between the okstratr observer and the
built-in Agents dashboard. It no longer changes the Graph tab. Your
existing choice is kept.

### Fixed

- **Safer page window** — page content shown in the Graph page window is
cleaned before it is displayed, so scripts embedded in a wiki page can't run.
- **Graph stays current** — adding or deleting a wiki page updates the Graph
and the page list without a reload, and switching back to a workspace that
changed in the meantime shows its latest pages.
- **Graph after a restart** — opening or reloading Switch Bay while Curiosity
Engine is restarting (for example right after switching workspaces) now
waits for it instead of showing an error.

## 2026-10-08 — v0.13.2

**Migration:** none. **Breaking:** none.
Expand Down
3 changes: 2 additions & 1 deletion docs/THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ obligation arises as we do not modify their files.

- **Graph / wiki-view** (`frontend/src/widgets/graph/static/*.js`) is a
fork of **curiosity-engine**'s wiki-view, used under the **MIT License**
(Copyright (c) 2026, curiosity-engine authors). Full MIT text below.
(Copyright (c) 2026, curiosity-engine authors), and now ships only in the
VS Code extension's graph webview. Full MIT text below.
- **Knowledge Atlas** (`frontend/src/widgets/graph/static/vendor/knowledge-atlas.js`)
is the first-party IIFE from curiosity-engine's `packages/knowledge-atlas`,
same MIT license.
Expand Down
48 changes: 34 additions & 14 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Architecture (v0.13)
# Architecture (v0.14)

How Switchbay **hosts** first-party skills today. Product vocabulary and
data flows stay in [`concepts-and-data-flow.md`](concepts-and-data-flow.md).
Expand Down Expand Up @@ -28,8 +28,11 @@ Graph / Agents skill surfaces.
non-loopback rejected).
- Injects `X-CE-Host: switchbay` / `X-Okstratr-Host: switchbay`.
- Vite dev proxies `/embed` to the daemon.
- Settings → **Proxied skill embeds** switches Graph → CE and Agents →
okstratr observer; turning it off restores built-ins. The proxy itself
- The **Graph** tab is always Curiosity Engine's own viewer, mounted
through `/embed/ce`. Switchbay has no built-in graph viewer.
- Settings → **Agents: okstratr observer** (`proxied_skill_embeds`) only
chooses the Agents surface: okstratr observer when on, the built-in
Agents dashboard when off. It does not affect Graph. The proxy itself
stays on either way.

## Same-document skill mount
Expand All @@ -42,14 +45,30 @@ Graph / Agents skill surfaces.
- Implementation: `frontend/src/widgets/embed/` (`ProxiedSkillPanel`,
`embedMount.ts`).

## Dual-mount Graph

- Shell left column: CE Pages|Files (`CeSidebarSlot`) persists across
Graph / Agents / Editor when proxied embeds are on for a CE workspace.
- Graph pane: atlas canvas only (`CeAtlasEmbed`); keeps the atlas warm
when you leave the tab; `atlas-cache.js` + workspace key for cold Graph cache.
- Prefers `window.CEEmbed.create` / `mount` when CE provides it; one
session survives Graph unmount.
## Graph comes from Curiosity Engine

- Graph, the wiki page list, page modal (incl. note editing), search,
type filters, Atlas/Classic, keybindings and curation replay are all
CE's viewer, loaded through `/embed/ce` with `window.CEEmbed.create`.
- `GET /api/settings` reports `ce_graph: {installed, has_wiki}`.
CE not installed → the Graph tab is hidden and Graph shortcuts fall back
to the Editor. Installed but no `wiki/` yet → a short placeholder.
- Shell left column: CE Pages (`CeSidebarSlot`) above Switchbay's own
Files | Sources browser. The Files uploader, RAG, Agents and Library do
not depend on CE.
- Graph pane: canvas only (`CeAtlasEmbed`, wrapped by `CeGraphSurface`);
one `CeEmbedSession` per workspace shares `data.json` between sidebar and
canvas, parks the canvas across tab switches and remounts it on wiki
`files_changed`.
- Host shims (`ceHostBridge.ts`, `ceEmbedShell.html`): page HTML is
sanitized before CE's modal renders it; KaTeX, table → Sheet / Plot and
the slideshow button are added to CE's modal; CE's split panel is routed
to `/api/workspaces/split`; Procedure / Execution label-type rows; the
sidebar `+` opens Switchbay's upload dialog (`/api/upload-vault`); the
sidebar header's `{{WORKSPACE}}` placeholder is filled from `data.json`.
- The forked viewer under `frontend/src/widgets/graph/` is kept **only**
for the VS Code extension's graph webview (`src/webview-graph.ts`); the
PWA never imports it.

## Core-skills auto-start

Expand All @@ -69,7 +88,8 @@ Graph / Agents skill surfaces.
`local_ingest` (prefer CE drop-ingest endpoint; rail LLM only when
run metadata opts in).

## Built-in Graph and Agents
## Built-in Agents

Built-in GraphTab, AgentDashboardTab, and filebrowser stay available.
Proxied skill embeds are opt-in in Settings.
The built-in Agents dashboard and file browser stay available; turning
off **Agents: okstratr observer** in Settings selects the built-in
dashboard. There is no built-in Graph.
2 changes: 1 addition & 1 deletion docs/license-risk-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ what was remediated.
|------|------|--------|
| Proprietary code in the dependency tree | `@univerjs-pro/*` (Univer commercial tier) | ✅ **RESOLVED** — dropped `@univerjs/presets`; built on granular Apache-2.0 packages; Pro tree fully removed (0 in lockfile/bundle); Sheet tab verified working |
| No aggregated attribution file | ~460 MIT/BSD/Apache/ISC deps require notice preservation | ✅ **RESOLVED** — `docs/THIRD-PARTY-NOTICES.md` generated |
| Forked MIT code without its notice | graph static JS forked from curiosity-engine (MIT) | ✅ **RESOLVED** — MIT attribution header added to the 5 forked files + NOTICES entry |
| Forked MIT code without its notice | graph static JS forked from curiosity-engine (MIT) | ✅ **RESOLVED** — MIT attribution header added to the forked files + NOTICES entry (since 0.14 the fork ships only in the VS Code graph webview; `edit.js` and `sidebar.js` were removed) |
| Undocumented dual-license elections | jszip, dompurify, certifi, tqdm | ✅ **RESOLVED** — recorded in NOTICES |

**No GPL / AGPL / LGPL / SSPL anywhere** in either dependency tree — the
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "switchbay-frontend",
"private": true,
"version": "0.13.2",
"version": "0.14.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
36 changes: 26 additions & 10 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import { installKeyRegistry, registerChord, registerCombo } from "./keys";
import { RailSocket, type Mode, type Selection, type ServerMessage, type TabSpec, type Workspaces } from "./ws";
import { applyWebPolicy, bindWebPolicy, loadWebPolicy } from "./lib/webPolicy";
import { SelectionProvider } from "./selection/SelectionContext";
import "./widgets/graph/load"; // window.Sidebar/Subgraph/Modal/Graph + ce-graph.css
import "./palette.css"; // theme vars + wiki-type palette + shared markdown view
import { useCeGraph } from "./widgets/embed/useProxiedSkillEmbeds";
import { resetCeEmbedSession } from "./widgets/embed/ceEmbedSession";
import type { GraphData } from "./widgets/graph/types";
import Walkthrough, { maybeAutoStartWalkthrough } from "./walkthrough/Walkthrough";
import UploadVaultDialog from "./widgets/upload/UploadVaultDialog";
Expand Down Expand Up @@ -415,11 +417,18 @@ export default function App() {
// Tab scoping (control surface v1): thread-scoped tabs render only
// while their thread is focused. Filtered client-side so a thread
// switch shows/hides them instantly, no round-trip.
// Graph is Curiosity Engine's viewer; without a CE install the tab
// (and every route into it) is hidden rather than shown broken.
const ceGraph = useCeGraph();
const graphHidden = ceGraph !== null && !ceGraph.installed;
const graphHiddenRef = useRef(graphHidden);
graphHiddenRef.current = graphHidden;
const visibleTabs = useMemo(
() => mode.tabs.filter(
(t) => !t.thread || t.thread === focusedThread,
(t) => (!t.thread || t.thread === focusedThread)
&& !(graphHidden && t.kind === "graph"),
),
[mode, focusedThread],
[mode, focusedThread, graphHidden],
);
// If the active tab just went out of scope (thread switch), fall
// back to the first visible one.
Expand Down Expand Up @@ -1820,6 +1829,10 @@ export default function App() {
};
}, [setSelection]);

/** A new workspace gets a fresh CE embed session (Power remounts the
* sidebar per workspace too; this covers Zen, which has none). */
useEffect(() => () => { resetCeEmbedSession(); }, [workspace]);

/** CE's sidebar + graph.js still write `#page=<id>` to window.location.hash
* when the user clicks. Translate that into a selection-layer dispatch. */
useEffect(() => {
Expand All @@ -1846,13 +1859,13 @@ export default function App() {
return () => window.removeEventListener("hashchange", onHash);
}, [setSelection]);

/** Highlight the active row in the Browser sidebar regardless of which
* tab is active. Modal/graph effects live in GraphTab (they can only
* fire when that tab is mounted). */
/** Highlight the active row in CE's page sidebar regardless of which
* tab is active. Modal/graph effects live in CeAtlasEmbed (they can
* only fire when the Graph canvas is mounted). */
useEffect(() => {
if (!graphData) return;
if (!selection || selection.kind !== "page") return;
try { window.Sidebar.setActive(selection.id); } catch { /* ignore */ }
try { window.Sidebar?.setActive(selection.id); } catch { /* ignore */ }
}, [selection, graphData]);

/** One-shot tips to drop into the rail the first time the user opens
Expand Down Expand Up @@ -1933,8 +1946,8 @@ export default function App() {
values: detail.values,
});
switchToKindRef.current?.("univer");
// Don't call window.Modal.close() — its onClose callback
// (registered in GraphTab.tsx) nulls selection back to null,
// Don't call window.Modal.close() — closing the doc modal
// (CeAtlasEmbed watches it) nulls selection back to null,
// which clobbers the table-data we just set. The modal stays
// open behind the now-active Sheet tab and the user can dismiss
// it on next visit.
Expand Down Expand Up @@ -2410,7 +2423,10 @@ export default function App() {
}, [workspace, pushToast]);

const switchToKind = useCallback(
(kind: string) => {
(kindIn: string) => {
// No CE → no Graph tab: page routes that would open the graph's
// doc modal open the page in the Editor instead.
const kind = kindIn === "graph" && graphHiddenRef.current ? "markdown" : kindIn;
// Zen: the graph is always visible (left pane); every other
// kind lands on the right-pane surface host. Reaching a kind
// clears a pending artifact of that kind — the user got there.
Expand Down
14 changes: 3 additions & 11 deletions frontend/src/center/builtinTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { lazy } from "react";
import SketchErrorBoundary from "../widgets/sketch/ErrorBoundary";
import { registerTabKind, type TabComponent } from "./tabRegistry";
import ProxiedSkillPanel from "../widgets/embed/ProxiedSkillPanel";
import CeAtlasEmbed from "../widgets/embed/CeAtlasEmbed";
import CeGraphSurface from "../widgets/embed/CeGraphSurface";
import { useProxiedSkillEmbeds } from "../widgets/embed/useProxiedSkillEmbeds";

/**
Expand All @@ -23,7 +23,6 @@ import { useProxiedSkillEmbeds } from "../widgets/embed/useProxiedSkillEmbeds";
* entries can keep a uniform `(ctx) => JSX` shape.
*/

const GraphTab = lazy(() => import("../widgets/graph/GraphTab"));
const EditorTab = lazy(() => import("../widgets/editor/EditorTab"));
const DuckDBTab = lazy(() => import("../widgets/duckdb/DuckDBTab"));
const SheetTab = lazy(() => import("../widgets/sheet/SheetTab"));
Expand All @@ -43,15 +42,8 @@ const ReportDocTab = lazy(() => import("../widgets/library/ReportDocTab"));
const ThrustersTab = lazy(() => import("../widgets/thrusters/ThrustersTab"));
const OwidTab = lazy(() => import("../widgets/owid/OwidTab"));

const GraphAdapter: TabComponent = ({ graphData, graphError }) => {
const proxied = useProxiedSkillEmbeds();
if (proxied === null) {
return <div className="sy-placeholder"><p>Loading…</p></div>;
}
// Graph = CE atlas embed skin (dedicated mount; not full HTML remount).
if (proxied) return <CeAtlasEmbed />;
return <GraphTab data={graphData} error={graphError} />;
};
/** Graph = Curiosity Engine's own viewer, mounted through /embed/ce. */
const GraphAdapter: TabComponent = () => <CeGraphSurface />;
const EditorAdapter: TabComponent = ({ tab }) => <EditorTab tab={tab} />;
const DuckDBAdapter: TabComponent = () => <DuckDBTab />;
const SheetAdapter: TabComponent = () => <SheetTab />;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* in webviews should call this interface; the PWA implements it with
* REST/WS, the plugin with `acquireVsCodeApi().postMessage`.
*
* This file is the contract. Wiring GraphTab / AgentDashboard / preview
* This file is the contract. Wiring the Graph embed / AgentDashboard / preview
* onto it is a later spike step — do not fetch the daemon from a
* plugin webview in the meantime.
*/
Expand Down
Loading
Loading