Skip to content

fix(desktop): workspace scoping + returning-user org recovery + signal drill-down - #1

Open
Bennettxai wants to merge 2 commits into
Miosa-osa:mainfrom
Bennettxai:fix/desktop-workspace-scoping
Open

fix(desktop): workspace scoping + returning-user org recovery + signal drill-down#1
Bennettxai wants to merge 2 commits into
Miosa-osa:mainfrom
Bennettxai:fix/desktop-workspace-scoping

Conversation

@Bennettxai

@Bennettxai Bennettxai commented Jul 24, 2026

Copy link
Copy Markdown

Problem

In the desktop shell, the data modules (nodes, graph, timeline, heatmap, activity) rendered empty for a real workspace, and returning users saw every module empty. The signal drill-down also errored with "signal not found."

Root causes (all: the active scope was dropped before reaching the engine)

  1. Org id used as tenant id. bootstrap()/refreshWorkspaces() called listWorkspaces(orgId), but that arg is tenant. Selecting a real org (operatoros) sent ?tenant=operatoros, matched zero workspaces, and blanked the UI.

  2. Graph + node files unscoped. loadGraph() and getNodeFiles() hit /api/workspace, /api/optimal/graph, /api/optimal/nodes/:slug/files with no workspace param, always reading the empty default workspace. Node slugs are not globally unique, so scope is mandatory.

  3. Returning users stuck on the compat default org. Before organizations were first-class, the app auto-selected and persisted the default org. Fix fix(desktop): workspace scoping + returning-user org recovery + signal drill-down #1's re-selection only triggered when the saved org was invalid; default is valid, so returning users stayed parked on the empty compatibility org and saw every module empty. This was the reported "nothing works."

  4. Signal drill-down 404. /api/signals/:id is keyed by the context id and workspace-scoped, but the node-files payload dropped the id it already SELECTs (the UI parsed the filename instead, which never matches) and getSignal() omitted the workspace.

Fix

  • listWorkspaces() takes an options object and filters by ?organization; added organization_id to the Workspace type.
  • bootstrap() prefers a real organization, and now also upgrades away from the empty default org when a real one exists, so returning users self-heal on next load.
  • loadGraph(workspace) and getNodeFiles(slug, workspace) thread the active workspace through every /api/workspace and /api/optimal/* call; the /graph route remounts on workspace switch.
  • optimal_node_files now returns each signal's id; getSignal(id, workspace) passes the scope; the Nodes page drills in with the real id.

Verification (live engine on :4200)

  • svelte-check: 0 errors (4 pre-existing warnings). Engine mix compile: clean.
  • Restoring the old default/default localStorage state now recovers to OperatorOS + a populated workspace and renders the node tree.
  • /graph renders 262 nodes / 420 edges for a populated workspace (was 0).
  • All 5 workspaces populate nodes, graph, activity/timeline/heatmap, and search.
  • Note: the running engine release must be rebuilt for the node-files id (fix #4) to appear; the change compiles and the /api/signals/:id?workspace= contract is verified manually.

Out of scope / follow-ups

  • memory and wiki are empty by data-state (no promoted facts, no curated wiki pages), not a bug.
  • +layout.svelte createWorkspace({ tenant: $activeOrgId }) has the same org-vs-tenant mixup on the write path and no organization field. Left for a separate pass.

🤖 Generated with Claude Code

Bennettxai and others added 2 commits July 24, 2026 14:48
The desktop shell showed empty modules (nodes, graph, timeline, heatmap)
for any real workspace. Two root causes, both the same class of bug: the
active workspace scope was dropped before hitting the engine, so requests
fell back to the empty "default" compatibility workspace.

1. workspace store: bootstrap()/refreshWorkspaces() called
   listWorkspaces(orgId), but that function's first arg is `tenant`, not
   organization. An org id is not a tenant id (every workspace shares
   tenant "default" but belongs to a specific organization), so selecting
   a real org (e.g. operatoros) sent ?tenant=operatoros and matched zero
   workspaces, emptying the whole UI. listWorkspaces now takes an options
   object and filters by ?organization. bootstrap() also now prefers a
   real organization over the "default" compatibility org on first load.

2. knowledge graph + node files: loadGraph() and getNodeFiles() called
   /api/workspace, /api/optimal/graph and /api/optimal/nodes/:slug/files
   with no workspace param, so they always read the empty default
   workspace. Node slugs are not globally unique across workspaces, so the
   scope is mandatory. Both now thread the active workspace through every
   call, and the /graph route remounts on workspace switch.

Verified against a live engine: all 5 workspaces now populate nodes,
graph, activity/timeline/heatmap and search. svelte-check: 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x signal drill-down

Follow-up to the workspace-scoping fix, addressing "every module is empty"
for returning users.

1. Stuck on the compat "default" org. Before organizations were first-class,
   the app auto-selected and persisted the "default" org. The previous fix
   only re-selected when the saved org was *invalid*, but "default" is valid,
   so returning users stayed parked on the empty compatibility org and every
   data module (nodes, graph, timeline, heatmap, activity) rendered empty.
   bootstrap() now also upgrades away from "default" to a real org when one
   exists, so a returning user self-heals on next load. Verified live:
   restoring the old default/default localStorage state now recovers to
   OperatorOS + a populated workspace and renders the node tree.

2. Signal drill-down returned "signal not found". /api/signals/:id is keyed
   by the context id and is workspace-scoped, but the node-files payload
   dropped the id it already SELECTs (the UI fell back to parsing the
   filename, which never matches) and getSignal() omitted the workspace.
   optimal_node_files now returns `id`; getSignal(id, workspace) passes the
   scope; the workspace page uses the file's real id. svelte-check 0 errors;
   engine recompiles clean (the running release must be rebuilt for the
   node-files id to appear).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Bennettxai Bennettxai changed the title fix(desktop): scope workspace data reads to the active workspace fix(desktop): workspace scoping + returning-user org recovery + signal drill-down Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant