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.
|
||||
43
docs/HYPRLAND.md
Normal file
43
docs/HYPRLAND.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Hyprland integration
|
||||
|
||||
The installed `nocal-hyprland` launcher chooses Kitty, foot, WezTerm, or
|
||||
Alacritty and gives its Wayland window the dedicated `nocal` class/app-id. Set
|
||||
`NOCAL_TERMINAL` to an executable path to override that order.
|
||||
|
||||
Hyprland 0.55 moved its configuration from hyprlang to Lua. Add this to
|
||||
`~/.config/hypr/hyprland.lua` for a large, centered calendar on `SUPER+C`:
|
||||
|
||||
```lua
|
||||
hl.bind("SUPER + C", hl.dsp.exec_cmd("nocal-hyprland"), {
|
||||
description = "Open calendar"
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
name = "nocal-calendar",
|
||||
match = { class = "nocal" },
|
||||
float = true,
|
||||
center = true,
|
||||
size = { "monitor_w*0.82", "monitor_h*0.82" },
|
||||
persistent_size = true,
|
||||
no_dim = true,
|
||||
})
|
||||
```
|
||||
|
||||
For Hyprland 0.54 and older hyprlang configurations, the equivalent is:
|
||||
|
||||
```ini
|
||||
bind = SUPER, C, exec, nocal-hyprland
|
||||
windowrule = float, class:^(nocal)$
|
||||
windowrule = center, class:^(nocal)$
|
||||
windowrule = size 82% 82%, class:^(nocal)$
|
||||
windowrule = persistentsize, class:^(nocal)$
|
||||
windowrule = nodim, class:^(nocal)$
|
||||
```
|
||||
|
||||
Nocal deliberately does not request a particular opacity or color. Configure
|
||||
those on the terminal and Nocal will inherit them; the application uses default
|
||||
foreground/background plus the ANSI semantic palette.
|
||||
|
||||
The syntax above follows the current official [Hyprland bind documentation]
|
||||
(https://wiki.hypr.land/Configuring/Basics/Binds/) and [window-rule
|
||||
documentation](https://wiki.hypr.land/Configuring/Basics/Window-Rules/).
|
||||
92
docs/PRODUCT.md
Normal file
92
docs/PRODUCT.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Nocal product and interaction specification
|
||||
|
||||
Nocal is a local-first, keyboard-driven month calendar for Linux terminals. Its
|
||||
visual target is the information density of a desktop month view without
|
||||
pretending that a terminal is a pixel canvas. The terminal emulator owns the
|
||||
typeface and palette; Nocal owns hierarchy, spacing, and restrained emphasis.
|
||||
|
||||
## Product principles
|
||||
|
||||
1. **The month is the home screen.** Launching Nocal immediately shows six
|
||||
complete Monday-first weeks. There is no dashboard or splash screen.
|
||||
2. **Useful at a glance.** Every visible day shows as many appointments as fit,
|
||||
ordered as all-day first and then by start time. A final `+N more` line is
|
||||
used instead of clipping silently.
|
||||
3. **Keyboard-native.** Arrow keys and Vim keys move by day or week; month and
|
||||
today jumps are single keystrokes. Every action remains discoverable in the
|
||||
footer and help overlay.
|
||||
4. **Terminal-native aesthetics.** Nocal uses default foreground/background and
|
||||
the terminal's ANSI semantic colors. It never paints a fixed RGB theme over
|
||||
the user's Kitty, foot, Alacritty, or WezTerm theme.
|
||||
5. **Local-first, sync-ready.** The canonical domain model is independent of
|
||||
iCalendar files and of any future remote API. Sync engines translate into
|
||||
domain objects and never leak provider-specific types into the UI.
|
||||
6. **Fast enough to feel instant.** Startup should remain below 50 ms for a
|
||||
normal local calendar and redraw should be flicker-free at interactive
|
||||
resize rates.
|
||||
|
||||
## Month view
|
||||
|
||||
The full terminal is treated as a responsive canvas:
|
||||
|
||||
```text
|
||||
JULY 2026 Today Fri 17 Jul
|
||||
Mon Tue Wed Thu Fri Sat Sun
|
||||
┌────────────┬────────────┬────────────┬────────────┬────────────┬────────────┬────────────┐
|
||||
│ 29 │ 30 │ 1 │ 2 │ 3 │ 4 │ 5 │
|
||||
│ │ │ 09:30 Team │ │ All day … │ │ │
|
||||
├────────────┼────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
|
||||
│ … │
|
||||
└─────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
←↓↑→ move PgUp/PgDn month t today ? help q quit 2 appointments
|
||||
```
|
||||
|
||||
- At 100 columns and above, Nocal draws the full bordered grid.
|
||||
- Narrow cells abbreviate times and ellipsize summaries by display width.
|
||||
- When height is constrained, appointment lines are reduced before structural
|
||||
chrome. At extremely small sizes a clear minimum-size message replaces a
|
||||
broken grid.
|
||||
- Days outside the focused month remain visible but dim.
|
||||
- The selected date is reverse-video, today is bold/underlined, and collisions
|
||||
use semantic ANSI accents. These attributes work on monochrome terminals.
|
||||
- The footer reports the selected day's appointment count and the most useful
|
||||
keys for the available width.
|
||||
|
||||
## Interaction map
|
||||
|
||||
| Action | Keys |
|
||||
| --- | --- |
|
||||
| Previous/next day | `Left` / `Right`, `h` / `l` |
|
||||
| Previous/next week | `Up` / `Down`, `k` / `j` |
|
||||
| Previous/next month | `PageUp` / `PageDown`, `p` / `n` |
|
||||
| Jump to today | `t` |
|
||||
| Next/previous appointment | `Tab` / `Shift-Tab` |
|
||||
| Read focused appointment | `Enter` |
|
||||
| Browse inside reader | arrows, `h j k l`, `Tab` / `Shift-Tab` |
|
||||
| Add appointment | `a` |
|
||||
| Edit focused appointment | `e` |
|
||||
| Delete focused appointment | `d`, then `y` to confirm |
|
||||
| Undo/redo successful mutation | `u` / `Ctrl-R` |
|
||||
| Move between editor fields | `Tab` / `Shift-Tab`, arrows |
|
||||
| Save/cancel editor | `Ctrl-S` / `Esc` |
|
||||
| Return to month/unfocus | `Esc` |
|
||||
| Help | `?` |
|
||||
| Quit | `q` |
|
||||
|
||||
The first usable foundation supports day and appointment navigation, a
|
||||
full-frame reader, validated add/edit/delete forms, and atomic `.ics` writes.
|
||||
Dense days keep every appointment keyboard-reachable even when all lines do
|
||||
not fit in the cell. Unknown or unsupported iCalendar content makes a source
|
||||
read-only instead of being discarded. Saves reject external changes, retain a
|
||||
last-known-good backup, and feed bounded session undo/redo history. The next
|
||||
local-data work adds `/` search, calendar visibility toggles, and recurring-event
|
||||
and time-zone expansion.
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Information is never encoded by color alone.
|
||||
- `NO_COLOR` and terminals without color remain fully usable.
|
||||
- Borders and icons have ASCII fallbacks; Unicode width is calculated rather
|
||||
than assumed.
|
||||
- Focus is always visible and keyboard operation is complete.
|
||||
- Motion is limited to direct redraws; there are no decorative animations.
|
||||
50
docs/ROADMAP.md
Normal file
50
docs/ROADMAP.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Roadmap
|
||||
|
||||
## Foundation — current milestone
|
||||
|
||||
- C++20 domain model and deterministic six-week month layout
|
||||
- Responsive, theme-native ANSI month view with keyboard navigation
|
||||
- Appointment focus, dense-day traversal, and full-frame detail reader
|
||||
- Validated add/edit/delete forms with confirmation and failure rollback
|
||||
- Guarded atomic `.ics` persistence, advisory locking, and round-trip tests
|
||||
- Bounded session undo/redo, exact external-change detection, and backup recovery
|
||||
- Meson build, Nix development shell, desktop entry, and Hyprland launcher
|
||||
- Seeded sample data when explicitly requested, never silent data mutation
|
||||
|
||||
## 0.1 — complete local calendar
|
||||
|
||||
- Recurrence rules, exclusions, time zones, multi-day presentation
|
||||
- Search, agenda view, calendar toggles, import/export, configurable week start
|
||||
- Screen-reader-friendly linear view and full Unicode display-width handling
|
||||
|
||||
## 0.2 — durable cache and provider boundary
|
||||
|
||||
- SQLite WAL cache and migration framework
|
||||
- Background job queue, immutable UI snapshots, observable sync status
|
||||
- Secret Service credentials and OAuth callback helper
|
||||
- Generic `SyncProvider` contract plus a fake provider test suite
|
||||
|
||||
## 0.3 — CalDAV
|
||||
|
||||
- Discovery and incremental sync for Nextcloud and standards-compliant servers
|
||||
- ETag-aware writes, offline mutation queue, conflict UI
|
||||
- TLS and failure-path integration tests
|
||||
|
||||
## 0.4 — hosted providers
|
||||
|
||||
- Google Calendar adapter with incremental synchronization
|
||||
- Microsoft 365 adapter over Microsoft Graph delta queries
|
||||
- Account/calendar management UI and per-calendar ANSI identity
|
||||
|
||||
## 1.0 — distribution quality
|
||||
|
||||
- Stable configuration/data format and documented recovery procedures
|
||||
- Nomarchy package, Hyprland defaults, man page, shell completions
|
||||
- Performance budgets exercised against large and adversarial calendars
|
||||
- Fuzzed iCalendar parser, accessibility review, translations
|
||||
|
||||
## Quality gates
|
||||
|
||||
Every milestone requires clean warning-enabled builds, unit and integration
|
||||
tests, sanitizer runs, terminal-state recovery after signals/errors, no network
|
||||
access without explicit account setup, and no loss of unknown calendar data.
|
||||
Reference in New Issue
Block a user