refactor(webapp): drop AiProposalInbox usages from 9 module pages

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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-20 16:18:54 +02:00
parent 698ffe797c
commit 78bfea452a
10 changed files with 71 additions and 31 deletions

View file

@ -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:<userId>` 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<string>();
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<string, unknown>) => {
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:<userId>` 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<string, unknown>;
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 =

View file

@ -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 @@
<MissionGrantDialog mission={selected} bind:open={grantDialogOpen} />
{/if}
<h3 class="section-title">Vorschläge zur Review</h3>
<AiProposalInbox missionId={selected.id} />
<h3 class="section-title">Iterationen</h3>
{#if selected.iterations.length === 0}
<p class="empty">Noch keine Iteration gelaufen.</p>

View file

@ -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 @@
</script>
<div class="goals-page">
<AiProposalInbox module="goals" />
<div class="header">
<button class="add-btn" onclick={() => (showEditor = true)}>
<PencilSimple size={14} weight="bold" /> Eigenes

View file

@ -23,7 +23,6 @@
import QuickEventPopover from '$lib/modules/calendar/components/QuickEventPopover.svelte';
import { ShareNetwork } from '@mana/shared-icons';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
import { ShareModal } from '@mana/shared-uload';
const calendarsCtx: { readonly value: Calendar[] } = getContext('calendars');
@ -222,9 +221,6 @@
<!-- Header -->
<CalendarHeader onNewEvent={handleNewEvent} />
<!-- AI proposals awaiting approval -->
<AiProposalInbox module="calendar" />
<!-- Calendar view (full width) -->
<div class="calendar-content">
{#if calendarViewStore.viewType === 'week'}

View file

@ -1,13 +1,9 @@
<script lang="ts">
import ListView from '$lib/modules/drink/ListView.svelte';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
</script>
<svelte:head>
<title>Drink - Mana</title>
</svelte:head>
<div class="px-4 pt-4">
<AiProposalInbox module="drink" />
</div>
<ListView />

View file

@ -8,7 +8,6 @@
import { MEAL_TYPE_LABELS, NUTRIENT_INFO, suggestMealType } from '$lib/modules/food/constants';
import type { MealWithNutrition, NutritionProgress } from '$lib/modules/food/types';
import { Plus, Clock, Fire } from '@mana/shared-icons';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
const allMeals = useAllMeals();
const allGoals = useAllGoals();
@ -44,8 +43,6 @@
</svelte:head>
<div class="space-y-6">
<AiProposalInbox module="food" />
<!-- Header -->
<div class="flex items-center justify-between">
<div>

View file

@ -18,7 +18,6 @@
import { reactionsStore } from '$lib/modules/news/stores/reactions.svelte';
import { articlesStore } from '$lib/modules/news/stores/articles.svelte';
import { feedCacheStore } from '$lib/modules/news/stores/feed-cache.svelte';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
import {
ALL_TOPICS,
type Topic,
@ -147,7 +146,6 @@
</svelte:head>
<div class="news-page">
<AiProposalInbox module="news" />
{#if !isOnboarded}
<!-- ─── Onboarding ───────────────────────────────────── -->
<header class="hero">

View file

@ -6,7 +6,6 @@
import { searchNotes, getPreview, formatRelativeTime } from '$lib/modules/notes/queries';
import { notesStore } from '$lib/modules/notes/stores/notes.svelte';
import { NOTE_COLORS } from '$lib/modules/notes/types';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
const allNotes$: Observable<Note[]> = getContext('notes');
@ -52,7 +51,6 @@
</svelte:head>
<div class="notes-page">
<AiProposalInbox module="notes" />
<header class="notes-header">
<div>
<h1 class="notes-title">Notes</h1>

View file

@ -1,13 +1,9 @@
<script lang="ts">
import ListView from '$lib/modules/places/ListView.svelte';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
</script>
<svelte:head>
<title>Places - Mana</title>
</svelte:head>
<div class="px-4 pt-4">
<AiProposalInbox module="places" />
</div>
<ListView navigate={() => {}} goBack={() => history.back()} params={{}} />

View file

@ -15,7 +15,6 @@
import OnboardingModal from '$lib/modules/todo/components/OnboardingModal.svelte';
import TodoPage from '$lib/modules/todo/components/pages/TodoPage.svelte';
import PagePicker from '$lib/modules/todo/components/pages/PagePicker.svelte';
import AiProposalInbox from '$lib/components/ai/AiProposalInbox.svelte';
import { todoSettings } from '$lib/modules/todo/stores/settings.svelte';
import type { PageConfig } from '$lib/modules/todo/stores/settings.svelte';
import { getTaskStats } from '$lib/modules/todo';
@ -238,9 +237,6 @@
</div>
</header>
<!-- AI proposals awaiting approval -->
<AiProposalInbox module="todo" />
<!-- Quick Add -->
<div class="quick-add-wrapper">
<QuickAddTask labels={allLabels} onShowSyntaxHelp={() => (showSyntaxHelp = true)} />