- Fragment model with validation, DTOs (immutable records), repository, service - Sidecar stdin/stdout JSON protocol for Tauri integration - DI wiring via ServiceCollectionExtensions - Scaffolded Journal.Api (not yet wired) Co-Authored-By: Warp <agent@warp.dev>
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
│ ├── 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
│ ├── Services/
│ │ ├── IFragmentService.cs Interface (business logic contract)
│ │ └── FragmentService.cs Validates, calls repo, maps to DTOs
│ ├── 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. Swap
InMemoryFragmentRepositoryfor SQLite/EF Core later 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.Core —
Microsoft.Extensions.DependencyInjection.Abstractions(interface-only, lightweight) - Journal.Sidecar —
Microsoft.Extensions.DependencyInjection(full container implementation) + referencesJournal.Core
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.
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 |
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.analyze → IAiService (future)
ai.chat
search.query → ISearchService (future)
To add a module:
- Create model, DTO, repository, and service in
Journal.Core/ - Register the new service in
ServiceCollectionExtensions.cs - Inject the service into
Entry.csand add cases to the action switch - No changes needed to
Command.csorApp.cs
Dependency Injection
ServiceCollectionExtensions.cs wires everything up. Any host (sidecar, API, tests) calls:
services.AddFragmentServices();
This registers:
IFragmentRepository→InMemoryFragmentRepository(singleton — one shared store)IFragmentService→FragmentService(transient — fresh instance per request)