journal/docs/frontend-csharp-backend-wiring.md
Jacob Schmidt 54bef33f0b Refactor frontend state to store-first architecture
- add and expand feature stores for entries, fragments, todos, lists, settings

- move CRUD logic into store helpers and simplify component state handling

- update SidePanel + button to create section-specific items

- switch fragment UX to view-first with edit/create modes

- add and update docs for store-based state management

- remove deprecated account route
2026-02-25 20:52:46 -06:00

219 lines
6.2 KiB
Markdown

# Wiring Frontend to the C# Backend
This document explains how to connect the `Journal.App` frontend to the C# backend in this repository.
## Current Backend Reality
In this repo today, the C# backend projects in `Journal.slnx` are:
- `Journal.Core`
- `Journal.Sidecar`
- `Journal.SmokeTests`
There is currently **no** `Journal.Api` project in the solution file, so the primary integration path is:
- Frontend (Svelte/Tauri) -> Tauri bridge -> `Journal.Sidecar` (stdin/stdout JSON protocol)
## Command Protocol (C#)
`Journal.Core.Entry.HandleCommandAsync` accepts a JSON command envelope and returns:
- success: `{ "ok": true, "data": ... }`
- failure: `{ "ok": false, "error": "..." }`
Command model (`Journal.Core/Models/Command.cs`):
```json
{
"action": "entries.list",
"correlationId": "optional-string",
"id": "optional",
"type": "optional",
"tag": "optional",
"payload": {}
}
```
Useful actions for frontend wiring:
- `entries.list`
- `entries.load`
- `entries.save`
- `search.entries`
- `vault.load_all`
- `vault.save_current_month`
- `db.status`
- `db.hydrate_workspace`
## Recommended Integration (Sidecar Bridge)
Use a small frontend client that sends commands through one bridge function. The bridge can be backed by:
- a Tauri command that talks to a managed sidecar process, or
- a local HTTP adapter (if you add one).
### 1. Define shared frontend command/response types
Create `Journal.App/src/lib/backend/types.ts`:
```ts
export type BackendCommand = {
action: string;
correlationId?: string;
id?: string;
type?: string;
tag?: string;
payload?: unknown;
};
export type BackendOk<T> = { ok: true; data: T };
export type BackendErr = { ok: false; error: string };
export type BackendResponse<T> = BackendOk<T> | BackendErr;
```
### 2. Create one backend client entrypoint
Create `Journal.App/src/lib/backend/client.ts`:
```ts
import { invoke } from "@tauri-apps/api/core";
import type { BackendCommand, BackendResponse } from "./types";
export async function sendCommand<T>(command: BackendCommand): Promise<T> {
const response = await invoke<BackendResponse<T>>("sidecar_command", { command });
if (!response.ok) {
throw new Error(response.error || "Backend command failed");
}
return response.data;
}
```
This keeps all UI code backend-agnostic.
### 3. Build domain helpers (entries example)
Create `Journal.App/src/lib/backend/entries.ts`:
```ts
import { sendCommand } from "./client";
export async function listEntries(dataDirectory?: string) {
return sendCommand<string[]>({
action: "entries.list",
payload: { dataDirectory }
});
}
export async function loadEntry(filePath: string) {
return sendCommand<{ filePath: string; content: string; section?: string }>({
action: "entries.load",
payload: { filePath }
});
}
export async function saveEntry(args: {
filePath?: string;
content: string;
title?: string;
section?: string;
date?: string;
}) {
return sendCommand<{ filePath: string }>({
action: "entries.save",
payload: args
});
}
```
### 4. Use client in UI state
In page/component code:
- on panel item click: call `loadEntry(filePath)`
- on editor save button: call `saveEntry({ filePath, content })`
- on app init: call `listEntries()` to populate list
## Tauri Bridge Notes
Your frontend should not spawn/process-manage the sidecar directly. Keep that in the Tauri layer.
Bridge responsibilities:
- start and keep one sidecar process alive
- write command JSON lines to sidecar stdin
- read stdout lines and map responses by `correlationId`
- return parsed response to frontend
- restart sidecar if it crashes
If you have not implemented this yet, create one Tauri command such as:
- `sidecar_command(command)`
and route all frontend calls through it.
## Vault/Auth Flow
Recommended startup sequence:
1. Prompt for vault password in UI.
2. Call `vault.load_all` (or `db.hydrate_workspace`) once.
3. Backend stores session password (`DatabaseSessionService`) for subsequent commands.
4. Continue with `entries.list`, `entries.load`, etc.
Do not store raw vault password in long-lived frontend state.
## Error Handling Pattern
Always normalize backend errors in one place:
- backend client throws `Error(message)` when `ok: false`
- UI catches and displays your custom modal
- include `correlationId` on commands for tracing/logging
## Optional HTTP Path (If You Add Journal.Api)
If you later add `Journal.Api` with `POST /api/command`, keep the same command envelope and swap transport only:
- replace `invoke("sidecar_command", ...)` with `fetch("/api/command", ...)`
- keep `sendCommand` interface unchanged
That lets UI code remain identical.
## Minimal Next Steps
1. Add `src/lib/backend/types.ts`, `client.ts`, `entries.ts`.
2. Wire `EditorPanel` save button to `entries.save`.
3. Wire `SidePanel` item load to `entries.load`.
4. Add vault unlock modal + `vault.load_all` on startup.
5. Keep all backend calls behind `sendCommand` only.
## Frontend Store Architecture (Current)
Current frontend uses feature stores in `Journal.App/src/lib/stores/`:
- `entries.ts` -> `entriesStore`
- `fragments.ts` -> `fragmentsStore`
- `todos.ts` -> `todoListsStore`, `todosStore`
- `lists.ts` -> `listsStore`
- `settings.ts` -> `settingsTags`, `settingsFragmentTypes`
Current pattern is store-first for most feature CRUD and parsing (especially fragments and todos), with UI components invoking store helpers.
## State/CRUD Gaps Still Needed
To fully standardize state management:
1. Move settings add/edit/remove logic into `settings.ts` helper functions (currently in route component code).
2. Add full CRUD helpers for `entries.ts` and `lists.ts` (update/remove/reorder, not only draft creation).
3. Make todo list metadata + todo items update atomically through a single store API wrapper.
4. Move calendar-created entries out of local component state into a dedicated calendar store.
5. Add persistence/hydration strategy between stores and backend (`entries.load/save`, `vault.load_all`, etc.).
## Recommended Rule
- Keep all feature data mutations in store helper APIs.
- Keep route/component files focused on view state and command orchestration.
- Keep backend transport (`sendCommand`) separate from pure local store mutation helpers, then compose both in thin feature services.