feat: initialize Nocal terminal calendar
This commit is contained in:
89
docs/ARCHITECTURE.md
Normal file
89
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Architecture
|
||||
|
||||
Nocal is split into four layers. Dependencies point inward, keeping remote sync
|
||||
and terminal rendering replaceable.
|
||||
|
||||
```text
|
||||
src/main.cpp
|
||||
├── tui/ ANSI rendering, input decoding, terminal lifecycle
|
||||
├── storage/ iCalendar file adapter (later: SQLite cache)
|
||||
└── domain/ Event, Calendar, civil dates, month layout and queries
|
||||
|
||||
future sync workers
|
||||
├── caldav/ Nextcloud and generic CalDAV
|
||||
├── google/ Google Calendar REST/OAuth
|
||||
└── graph/ Microsoft Graph/OAuth
|
||||
└──────────> domain/ through a SyncProvider interface
|
||||
```
|
||||
|
||||
## Domain
|
||||
|
||||
The domain layer uses C++20 `<chrono>` civil dates and time points. It knows
|
||||
nothing about escape sequences, files, OAuth, or HTTP. Month layout always
|
||||
produces 42 cells, starting on Monday, which keeps redraw geometry stable.
|
||||
|
||||
Events retain a stable UID, title, half-open start/end interval, all-day flag,
|
||||
optional descriptive fields, and calendar identity. Queries define overlap as
|
||||
`event.start < range.end && event.end > range.start`; this matters for events
|
||||
crossing midnight.
|
||||
|
||||
## Storage
|
||||
|
||||
Version 0.1 uses a user-owned iCalendar file, defaulting to
|
||||
`$XDG_DATA_HOME/nocal/calendar.ics` or `~/.local/share/nocal/calendar.ics`.
|
||||
The adapter unfolds content lines before parsing and escapes text on output.
|
||||
On POSIX systems, writes are serialized with an advisory lock, staged in a
|
||||
same-directory private temporary file, flushed, and atomically renamed over the
|
||||
destination. A load retains an immutable snapshot of the exact source bytes.
|
||||
Before saving, the writer compares that snapshot with the destination while it
|
||||
holds the same lock used for replacement, then checks again after staging and
|
||||
immediately before commit. This catches same-size and same-timestamp changes
|
||||
and fully serializes cooperating Nocal writers. The lock is advisory: an
|
||||
unrelated program can ignore it, so no portable filesystem API can provide a
|
||||
true compare-and-swap against every possible external writer.
|
||||
|
||||
Before an existing destination is replaced, its exact bytes are atomically
|
||||
written to the adjacent `.bak` file. Explicit backup restoration uses the same
|
||||
lock, source-revision check, private staging, and atomic replacement path; it
|
||||
does not consume the backup. The TUI mutates a copyable in-memory model and
|
||||
rolls it back if persistence fails. Its bounded undo/redo history contains only
|
||||
mutations that crossed the persistence boundary successfully.
|
||||
|
||||
The current writer deliberately refuses to mutate an existing file when the
|
||||
loader encounters information it cannot round-trip, including recurrence,
|
||||
alarms, attendees, time-zone identifiers, unknown properties, or malformed
|
||||
components. Browsing remains available. This conservative boundary is more
|
||||
important than partial editing because a successful-looking edit must not
|
||||
erase unrelated calendar data.
|
||||
|
||||
Provider synchronization will not write directly into this UI file. It will use
|
||||
a transaction-capable local cache and preserve remote ETags/sync tokens in a
|
||||
provider metadata table.
|
||||
|
||||
The planned cache schema has `calendars`, `events`, `event_instances`, and
|
||||
`sync_state`. Raw provider payloads are retained alongside normalized fields so
|
||||
an older Nocal cannot destroy fields it does not understand.
|
||||
|
||||
## Terminal backend
|
||||
|
||||
The initial backend depends only on POSIX `termios`, `poll`, and ANSI/ECMA-48.
|
||||
It uses the alternate screen, hides the cursor while rendering, restores all
|
||||
terminal state through RAII, and repaints from a complete frame buffer. A resize
|
||||
signal only sets a flag; terminal size and rendering are handled safely in the
|
||||
main loop.
|
||||
|
||||
Colors are semantic ANSI slots and default background, intentionally delegating
|
||||
actual RGB values to the terminal theme. This is both simpler and more native
|
||||
than shipping an application theme that fights the desktop palette.
|
||||
|
||||
## Sync roadmap
|
||||
|
||||
All providers implement the same conceptual operations: authenticate, list
|
||||
calendars, perform an incremental pull, push a local mutation, and resolve a
|
||||
conflict. CalDAV uses sync tokens/ETags, Google uses page/sync tokens, and
|
||||
Microsoft uses Graph delta links. Secrets belong in the Freedesktop Secret
|
||||
Service, never in the calendar database or command line.
|
||||
|
||||
Conflict resolution is deterministic: unchanged side wins; otherwise retain
|
||||
both versions and mark a conflict for the user. Network work happens outside the
|
||||
render loop and publishes immutable snapshots to it.
|
||||
Reference in New Issue
Block a user