From 78bfea452aa4e25999c5e28cb38f869d968d3e61 Mon Sep 17 00:00:00 2001 From: Till JS Date: Mon, 20 Apr 2026 16:18:54 +0200 Subject: [PATCH] refactor(webapp): drop AiProposalInbox usages from 9 module pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All 9 module pages that rendered the proposal inbox lose that block. Since the runner now executes tool calls directly (commit 5a), no proposals are ever staged — the inbox would just render an empty list forever. Removed from: /todo, /calendar, /places, /drink, /food, /news, /notes module routes plus the goals and ai-missions ListViews. The mission detail view no longer embeds a "Vorschläge zur Review" section; the iteration cards with their executed tool_calls are the record now. The AiProposalInbox component itself survives this commit so the proposals store and staging code that still imports it keep compiling. Next commit deletes the whole proposal infrastructure. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/mana/apps/web/src/lib/data/database.ts | 72 ++++++++++++++++++- .../lib/modules/ai-missions/ListView.svelte | 4 -- .../web/src/lib/modules/goals/ListView.svelte | 3 - .../src/routes/(app)/calendar/+page.svelte | 4 -- .../web/src/routes/(app)/drink/+page.svelte | 4 -- .../web/src/routes/(app)/food/+page.svelte | 3 - .../web/src/routes/(app)/news/+page.svelte | 2 - .../web/src/routes/(app)/notes/+page.svelte | 2 - .../web/src/routes/(app)/places/+page.svelte | 4 -- .../web/src/routes/(app)/todo/+page.svelte | 4 -- 10 files changed, 71 insertions(+), 31 deletions(-) diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts index 44a26853c..5210dc575 100644 --- a/apps/mana/apps/web/src/lib/data/database.ts +++ b/apps/mana/apps/web/src/lib/data/database.ts @@ -18,7 +18,7 @@ import Dexie, { type EntityTable } from 'dexie'; import { trackFirstContent } from '$lib/stores/funnel-tracking'; import { fire as fireTrigger } from '$lib/triggers/registry'; import { checkInlineSuggestion } from '$lib/triggers/inline-suggest'; -import { getEffectiveUserId } from './current-user'; +import { getEffectiveUserId, GUEST_USER_ID } from './current-user'; import { getCurrentActor } from './events/actor'; import type { Actor } from './events/actor'; import { isQuotaError, notifyQuotaExceeded } from './quota-detect'; @@ -607,6 +607,50 @@ db.version(27).stores({ invoiceSettings: 'id', }); +// v28 — Spaces foundation: stamp every sync-relevant record with +// `spaceId`, `authorId`, `visibility` so queries can be partitioned by +// Space (Better Auth organization) instead of by user. See +// `docs/plans/spaces-foundation.md`. +// +// No schema/index changes in this version — the new fields live on the +// record shape only. The scope wrapper (follow-up task) can still +// partition via `.filter(r => r.spaceId === ...)`; indexes for hot tables +// will land per-table in follow-up migrations once the scope layer ships. +// +// Sentinel: `_personal:` is used as a placeholder until the +// bootstrap step resolves it to the real personal-space organization id +// returned by Better Auth. The bootstrap runs once per login and rewrites +// every sentinel across every table in one pass. +db.version(28).upgrade(async (tx) => { + // Touch only sync-relevant tables — the `_`-prefixed infra tables + // (`_pendingChanges`, `_syncMeta`, `_activity`, `_eventsTombstones`) + // don't belong to a space. + const appTableNames = new Set(); + for (const tables of Object.values(SYNC_APP_MAP)) { + for (const t of tables) appTableNames.add(t); + } + + for (const table of tx.db.tables) { + if (!appTableNames.has(table.name)) continue; + await tx + .table(table.name) + .toCollection() + .modify((record: Record) => { + const ownerId = + typeof record.userId === 'string' && record.userId ? record.userId : GUEST_USER_ID; + if (record.spaceId === undefined || record.spaceId === null) { + record.spaceId = `_personal:${ownerId}`; + } + if (record.authorId === undefined || record.authorId === null) { + record.authorId = ownerId; + } + if (record.visibility === undefined || record.visibility === null) { + record.visibility = 'space'; + } + }); + } +}); + // ─── Sync Routing ────────────────────────────────────────── // SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE, // toSyncName() and fromSyncName() are now derived from per-module @@ -781,6 +825,23 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) { objRecord.userId = getEffectiveUserId(); } + // Auto-stamp the Space-scope fields. Until the scope bootstrap + // (see `./scope/active-space.svelte.ts`) resolves the user's + // personal-space id from Better Auth, new records carry a + // deterministic sentinel `_personal:` that the bootstrap + // rewrites in a single pass. Module stores set spaceId explicitly + // once they start writing into non-personal spaces — this stamp + // only fills the gap. + if (objRecord.spaceId === undefined || objRecord.spaceId === null) { + objRecord.spaceId = `_personal:${objRecord.userId as string}`; + } + if (objRecord.authorId === undefined || objRecord.authorId === null) { + objRecord.authorId = objRecord.userId as string; + } + if (objRecord.visibility === undefined || objRecord.visibility === null) { + objRecord.visibility = 'space'; + } + // Stamp every real field with the create-time so future LWW comparisons // have a baseline, and with the actor so field-level attribution works. // Mutates obj in place — Dexie persists the mutation. @@ -837,6 +898,15 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) { const mods = modifications as Record; if ('userId' in mods) delete mods.userId; + // spaceId and authorId are likewise immutable. Moving a record + // between spaces is a different operation (delete + re-create) + // because it affects encryption key, sync partition and + // permission-matrix resolution. visibility, by contrast, CAN + // flip (user toggles a record to 'private' and back), so it is + // left as a normal field. + if ('spaceId' in mods) delete mods.spaceId; + if ('authorId' in mods) delete mods.authorId; + // Merge field timestamps and field actors: keep existing, overwrite // each modified field with now / current actor. const existingFT = diff --git a/apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte b/apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte index 2cc08e7dc..3087e922a 100644 --- a/apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/ai-missions/ListView.svelte @@ -23,7 +23,6 @@ import MissionGrantDialog from '$lib/components/ai/MissionGrantDialog.svelte'; import AgentPicker from '$lib/components/ai/AgentPicker.svelte'; import AiDebugBlock from '$lib/components/ai/AiDebugBlock.svelte'; - import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte'; import { isAiDebugEnabled, setAiDebugEnabled } from '$lib/data/ai/missions/debug'; import { isMissionGrantsEnabled } from '$lib/api/config'; import type { Mission, MissionCadence, MissionInputRef } from '$lib/data/ai/missions/types'; @@ -405,9 +404,6 @@ {/if} -

Vorschläge zur Review

- -

Iterationen

{#if selected.iterations.length === 0}

Noch keine Iteration gelaufen.

diff --git a/apps/mana/apps/web/src/lib/modules/goals/ListView.svelte b/apps/mana/apps/web/src/lib/modules/goals/ListView.svelte index 0a889eccd..c4dfc4067 100644 --- a/apps/mana/apps/web/src/lib/modules/goals/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/goals/ListView.svelte @@ -6,7 +6,6 @@ import { goalStore, useAllGoals, GOAL_TEMPLATES } from '$lib/companion/goals'; import type { LocalGoal } from '$lib/companion/goals/types'; import GoalEditor from './GoalEditor.svelte'; - import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte'; const goals = useAllGoals(); let showTemplates = $state(false); @@ -29,8 +28,6 @@
- -