journal/README.md

13 KiB

Journal Backend (.NET)

A .NET 10 backend for the Project Journal app. Provides core journal functionality as a class library with a sidecar console app for Tauri integration and an optional HTTP API.

Project Structure

backend/
├── Journal.Core/              Class library — all business logic
│   ├── Models/
│   │   ├── Fragment.cs            Domain model (validated, owns Guid ID)
│   │   ├── Command.cs             Stdin command shape for sidecar protocol
│   │   ├── ParsedSection.cs       Parsed section model for entry parity work
│   │   ├── SectionTitles.cs       Canonical section title list (Python parity)
│   │   └── JournalEntry.cs        Entry domain (`date/raw_content/sections/fragments` + merge + markdown reconstruction)
│   ├── Dtos/
│   │   └── FragmentDtos.cs        Immutable records for API boundary
│   │       ├── FragmentDto            Read (what goes out)
│   │       ├── CreateFragmentDto      Create (what comes in)
│   │       └── UpdateFragmentDto      Update (partial, all fields optional)
│   ├── Repositories/
│   │   ├── IFragmentRepository.cs     Interface (data access contract)
│   │   ├── InMemoryFragmentRepository.cs  In-memory implementation (tests/dev)
│   │   └── FileFragmentRepository.cs      File-backed implementation (default)
│   ├── Services/
│   │   ├── IFragmentService.cs        Interface (business logic contract)
│   │   ├── FragmentService.cs         Validates, calls repo, maps to DTOs
│   │   ├── IEntrySearchService.cs     Entry search contract (content parity)
│   │   ├── EntrySearchService.cs      Searches decrypted `.md` entries by raw content query
│   │   ├── IJournalConfigService.cs   Config contract for path/vault/AI/speech settings parity
│   │   ├── JournalConfigService.cs    Env/default-backed config surface aligned with Python keys
│   │   ├── IAiService.cs              AI bridge contract (optional provider)
│   │   ├── DisabledAiService.cs       No-op AI provider for deterministic disabled mode
│   │   ├── PythonSidecarAiService.cs  Local Python sidecar adapter (stdin/stdout JSON)
│   │   ├── SidecarCli.cs              CLI runner (`vault` + `search`) used by Sidecar host
│   │   ├── JournalParser.cs           Date + section + checkbox + fragment parser slices (Phase 2)
│   │   ├── IVaultCryptoService.cs     Vault crypto contract
│   │   ├── VaultCryptoService.cs      AES-256-GCM + PBKDF2 compatibility layer
│   │   ├── IVaultStorageService.cs    Vault load/workflow contract
│   │   └── VaultStorageService.cs     Monthly naming + load/decrypt/extract workflow
│   ├── Entry.cs                   Command dispatcher (stdin/stdout)
│   ├── ServiceCollectionExtensions.cs  DI registration helper
│   └── Journal.Core.csproj
│
├── Journal.Sidecar/           Console app — Tauri sidecar bridge
│   ├── App.cs                     Boots DI container, runs Entry.RunAsync()
│   └── Journal.Sidecar.csproj     References Journal.Core
│
├── Journal.Api/               Web API — HTTP endpoint wrapper (optional)
│   ├── Program.cs
│   └── Journal.Api.csproj
│
└── README.md

Architecture

Each layer only knows about the one below it:

Sidecar (stdin/stdout)  ──┐
                          ├──►  Services (business logic)  ──►  Repositories (data access)
API (HTTP/JSON)  ─────────┘
  • Models — Domain objects with validation. The source of truth.
  • DTOs — Immutable records that cross the API boundary. Internal logic never leaks out.
  • Repositories — Where data lives. Current default is file-backed; can evolve to SQLite/EF Core without touching anything above.
  • Services — Business rules, validation, orchestration. Doesn't know about HTTP or stdin.
  • Entry — Transport adapter. Translates stdin/stdout JSON into service calls.

Dependencies

  • Journal.CoreMicrosoft.Extensions.DependencyInjection.Abstractions (interface-only, lightweight)
  • Journal.SidecarMicrosoft.Extensions.DependencyInjection (full container implementation) + references Journal.Core
  • Journal.ApiMicrosoft.AspNetCore.OpenApi + ASP.NET shared framework

Building

# Build everything (building Sidecar also rebuilds Core if changed)
dotnet build backend\Journal.Sidecar\Journal.Sidecar.csproj

# Build just the library
dotnet build backend\Journal.Core\Journal.Core.csproj

# Format code
dotnet format backend\Journal.Core\Journal.Core.csproj

Publishing

Publish as a single-file self-contained executable (no .NET runtime install needed):

dotnet publish backend\Journal.Sidecar\Journal.Sidecar.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true

Output: backend\Journal.Sidecar\bin\Release\net10.0\win-x64\publish\Journal.Sidecar.exe (~70MB, everything bundled)

To exclude debug symbols: add -p:DebugType=none

For a smaller build that requires .NET 10 on the target machine:

dotnet publish backend\Journal.Sidecar\Journal.Sidecar.csproj -c Release -r win-x64 -p:PublishSingleFile=true

Sidecar Protocol

The sidecar communicates over stdin/stdout using JSON lines. One JSON line in, one JSON line out. When run with no command-line args, this protocol mode is used by default.

Sidecar CLI

Journal.Sidecar also supports direct vault and search CLI commands:

# Load vaults into decrypted data workspace
dotnet run --project Journal.Sidecar/Journal.Sidecar.csproj -- vault load

# Save (rebuild) monthly vaults from decrypted markdown files
dotnet run --project Journal.Sidecar/Journal.Sidecar.csproj -- vault save

# Search entries (query + filters)
dotnet run --project Journal.Sidecar/Journal.Sidecar.csproj -- search "common text" --tag stress --type !TRIGGER --start-date 2026-02-01 --end-date 2026-02-28 --section Summary --checked "med taken"

Password prompt behavior:

  • If --password is omitted, CLI prompts with Vault password: (hidden input in terminal mode).
  • For automation/non-interactive use, pass --password <value>.

Optional path overrides:

  • --vault-dir <path>
  • --data-dir <path>
  • Env fallback: JOURNAL_VAULT_DIR, JOURNAL_DATA_DIR, JOURNAL_APP_DIR

Search CLI flags:

  • positional query (optional)
  • --tag / -t (repeatable)
  • --type / -y (repeatable)
  • --start-date / -s (yyyy-MM-dd)
  • --end-date / -e (yyyy-MM-dd)
  • --section / -sec
  • --checked / -chk (repeatable)
  • --unchecked / -uchk (repeatable)
  • --data-dir <path> (optional override)

Config Keys (Parity Surface)

JournalConfigService exposes and normalizes key settings expected from Python config:

  • Paths: JOURNAL_PROJECT_ROOT, JOURNAL_APP_DIR, JOURNAL_DATA_DIR, JOURNAL_VAULT_DIR, JOURNAL_LOG_DIR, JOURNAL_PID_FILE, JOURNAL_SERVER_CONTROL_FILE
  • Vault format: JOURNAL_MONTHLY_VAULT_FORMAT (default %Y-%m.vault)
  • AI endpoints/models: CLOUDAI_API_KEY, CLOUDAI_API_URL, LLAMA_CPP_URL, LLAMA_CPP_MODEL, LLAMA_CPP_TIMEOUT, EMBEDDING_API_URL, EMBEDDING_MODEL_NAME, MODEL_CONTEXT_TOKENS, CHUNK_TOKEN_BUDGET
  • AI bridge mode: JOURNAL_AI_PROVIDER (none or python-sidecar), JOURNAL_PYTHON_EXE, JOURNAL_AI_SIDECAR_PATH, JOURNAL_AI_TIMEOUT_MS
  • Speech/NLP: MICROPHONE_DEVICE_INDEX, SPEECH_RECOGNITION_ENGINE, WHISPER_MODEL_SIZE, JOURNAL_NLP_BACKEND

Command Format

{
  "action": "fragments.create",
  "id": null,
  "type": null,
  "tag": null,
  "payload": { "type": "!TRIGGER", "description": "stomach drop" }
}

Fields:

  • action — The operation to perform (e.g. fragments.list, fragments.create)
  • id — Target entity ID (for get/update/delete)
  • type / tag — Filter parameters (for search)
  • payload — Request body, deserialized into the appropriate DTO per action

Available Actions

Action Description Requires
fragments.list List all fragments
fragments.get Get fragment by ID id
fragments.create Create a new fragment payload (CreateFragmentDto)
fragments.update Update a fragment id, payload (UpdateFragmentDto)
fragments.delete Delete a fragment id
fragments.search Search by type/tag type and/or tag
entries.list List decrypted markdown entries in a data directory optional payload.dataDirectory
entries.load Load one entry file and return parsed metadata + raw content payload.filePath
entries.save Save/merge entry content to file (fragment append or full merge path) payload.content, optional payload.filePath, payload.mode
db.status Return DB key/schema compatibility status snapshot payload.password, optional payload.dataDirectory
db.initialize_schema Write SQL schema bootstrap (journal_schema.sql) for parity tables optional payload.dataDirectory
db.hydrate_workspace Perform C# DB hydration step for decrypted workspace (schema bootstrap + metadata) payload.password, optional payload.dataDirectory
config.get Return current backend config snapshot
ai.health Return AI bridge health/provider status
ai.summarize_entry Summarize one entry through AI provider payload.content, optional payload.fileStem
ai.summarize_all Summarize a set of entries through AI provider payload.entries[]
ai.chat Send chat prompt through AI provider bridge payload.prompt
ai.embed Generate embedding vector through AI provider bridge payload.content
search.entries Search decrypted entry content with optional parity filters payload.dataDirectory, optional payload.query, payload.section, payload.startDate, payload.endDate, payload.tags[], payload.types[], payload.checked[], payload.unchecked[]
vault.initialize Ensure vault directory exists payload.password, payload.vaultDirectory
vault.load_all Load/decrypt all monthly vaults into data directory payload.password, payload.vaultDirectory, payload.dataDirectory
vault.save_current_month Save only current month vault (optimized path) payload.password, payload.vaultDirectory, payload.dataDirectory, optional payload.nowUtc
vault.rebuild_all Rebuild all monthly vaults from decrypted .md data payload.password, payload.vaultDirectory, payload.dataDirectory
vault.clear_data_directory Clear decrypted data directory and recreate it payload.dataDirectory

Response Format

Success:

{ "ok": true, "data": { "id": "abc-123", "type": "!TRIGGER", "description": "...", "time": "...", "tags": [] } }

Error:

{ "ok": false, "error": "Description is required" }

Extending with New Modules

The Command class is generic — new modules use the same dot-notation pattern:

vault.unlock     → IVaultService (future)
vault.lock
entries.list     → IEntryService (future)
entries.create
ai.health        → IAiService (implemented bridge)
ai.summarize_*   → IAiService (implemented bridge)
ai.chat          → IAiService (implemented bridge)
ai.embed         → IAiService (implemented bridge)
db.status        → IJournalDatabaseService (in-progress DB parity)
search.query     → ISearchService (future)

To add a module:

  1. Create model, DTO, repository, and service in Journal.Core/
  2. Register the new service in ServiceCollectionExtensions.cs
  3. Inject the service into Entry.cs and add cases to the action switch
  4. No changes needed to Command.cs or App.cs

Dependency Injection

ServiceCollectionExtensions.cs wires everything up. Any host (sidecar, API, tests) calls:

services.AddFragmentServices();

This registers:

  • IFragmentRepositoryFileFragmentRepository (singleton — persisted fragment store)
  • IFragmentServiceFragmentService (transient — fresh instance per request)

Fragment Store Location

FileFragmentRepository persists data to:

  • default: .journal-sidecar/fragments.json under current working directory
  • override: JOURNAL_FRAGMENT_STORE_PATH environment variable

Legacy Vault Compatibility Note

The legacy Python placeholder file _init_vault.vault is treated as obsolete. During vault load, the C# backend ignores this file for decryption and removes it. This preserves compatibility while migrating older vault directories forward.