- 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
219 lines
6.2 KiB
Markdown
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.
|