Introduce provider-neutral HTTP contracts and public-client authorization-code orchestration with PKCE S256, strict HTTPS and loopback validation, constant-time state checks, secure OpenSSL entropy, and single-send token exchange semantics. Add independent protocol and adversarial suites covering RFC 7636, RFC3986 encoding, callback failures, response redaction, and no-retry behavior. Concrete networking, browser/listener, token parsing, Secret Service, and Graph integration remain separate follow-up slices.
181 lines
11 KiB
Markdown
181 lines
11 KiB
Markdown
# 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 weeks, Monday-first by default. `--week-start` can rotate the grid
|
|
to any weekday. 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` |
|
|
| Open/close 42-day agenda | `g`; `Esc` also closes |
|
|
| Choose agenda occurrence | arrows, `j` / `k`, `Tab` / `Shift-Tab`, then `Enter` |
|
|
| Shift agenda by 42 days | `PageUp` / `PageDown`, `p` / `n` |
|
|
| 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` |
|
|
| Search appointments | `/`, then type and press `Enter` |
|
|
| Choose search result | arrows, `j` / `k`, then `Enter` |
|
|
| Toggle calendar visibility | `c`, arrows or `j` / `k`, then `Enter` |
|
|
| Help | `?` |
|
|
| Quit | `q` |
|
|
|
|
The first usable foundation supports day and appointment navigation, a
|
|
full-frame reader, a read-only 42-day agenda, validated add/edit/delete forms,
|
|
atomic `.ics` writes, and explicit non-interactive calendar import/export.
|
|
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. Supported
|
|
recurring instances appear throughout the month, preserve civil time across
|
|
daylight-saving changes, and expose their source zone and whole-series mutation
|
|
semantics in the reader. A supported recurrence set combines `DTSTART`, rule
|
|
expansion, and compatible DATE or DATE-TIME `RDATE` starts, deduplicates starts,
|
|
and then removes `EXDATE` exclusions. `/` searches occurrence-aware title,
|
|
description, location, and calendar-ID matches in a finite ten-year window
|
|
centered on the selected date. `c` toggles session-only calendar visibility
|
|
consistently across the grid, appointment focus, and search without filtering
|
|
the model supplied to persistence. `g` opens an occurrence-aware agenda at the
|
|
selected month date; navigation is bounded to consecutive 42-day windows and
|
|
`Enter` returns to the exact occurrence in the month grid. `Esc` or `g` closes
|
|
the agenda without changing the month selection unless an occurrence was
|
|
chosen. Hidden calendars remain filtered there, while calendar selection and
|
|
search can overlay the agenda. Agenda navigation never persists state, and
|
|
add/edit/delete remain in the month workflow. Week-start selection rotates only
|
|
the month grid, header, and the visible range used by its month queries;
|
|
storage, agenda windows, and
|
|
recurrence rules are unchanged. The default remains Monday for backward
|
|
compatibility. The CLI accepts the exact lowercase values `monday`, `tuesday`,
|
|
`wednesday`, `thursday`, `friday`, `saturday`, and `sunday`. `RDATE` periods,
|
|
values incompatible with the event's `DTSTART` time basis or `TZID`, detached
|
|
`RECURRENCE-ID` overrides, and embedded `VTIMEZONE` definitions remain
|
|
read-only. Remaining local-data work includes detached overrides and rule
|
|
editing.
|
|
|
|
## Remote calendar foundation
|
|
|
|
Remote calendars must cross a durable local boundary before they are exposed to
|
|
the UI. That boundary is a private, provider-neutral SQLite cache in WAL mode,
|
|
with versioned migrations that either commit completely or leave the prior
|
|
schema intact. It keeps raw provider payloads, inactive calendars, and
|
|
tombstoned events so refreshes and older clients do not erase information. Each
|
|
opaque incremental cursor belongs to a particular calendar and time window and
|
|
is committed atomically with every pulled page. Normalized event instances feed
|
|
immutable snapshots rather than exposing provider response types to the month
|
|
view.
|
|
|
|
The cache also reserves transactional outbox and conflict records for future
|
|
offline writes, but remote mutation behavior is not part of this phase. OAuth
|
|
tokens and other credentials never belong in the calendar database; account
|
|
secrets will be held by the Freedesktop Secret Service.
|
|
|
|
The Microsoft sign-in design treats Nocal as a public desktop client: sign-in
|
|
will open the system browser, use the authorization-code flow with PKCE `S256`,
|
|
and return through a loopback redirect. Its application registration must be
|
|
multitenant for the intended organizational and personal Microsoft account
|
|
audience. Initial synchronization requests only delegated `Calendars.Read`, not
|
|
write access. Users can normally consent to that delegated permission, but
|
|
their organization's tenant policy may still require administrator consent or
|
|
deny the application. Nocal never embeds a client secret in its open-source
|
|
desktop binary.
|
|
|
|
The provider-neutral HTTP seam accepts an injected transport and can therefore
|
|
be exercised with deterministic fakes. It intentionally owns no generic retry
|
|
policy; provider operations must decide whether a retry is safe. This phase does
|
|
not make Office 365 usable yet. It includes no concrete HTTP transport, real
|
|
browser/loopback adapter, token JSON parser, Secret Service backend, Graph call,
|
|
or usable account setup. The next product slice supplies concrete desktop,
|
|
network, and secret-storage adapters, followed by read-only Graph calendar
|
|
discovery and delta synchronization. Remote writes remain gated on the
|
|
durability and conflict paths being exercised end to end.
|
|
|
|
## Local file interchange
|
|
|
|
Import and export are explicit command-line workflows. They never contact the
|
|
network and do not enter the interactive TUI.
|
|
|
|
- `nocal --import SOURCE --calendar TARGET` atomically merges events into the
|
|
target. Exact same-UID, same-field events are duplicates and are skipped. A
|
|
differing same-UID event, an empty or duplicate UID or invalid event interval
|
|
in either file, or content in either file that cannot be safely round-tripped
|
|
rejects the complete import before the target is written. A successful
|
|
merge keeps the target's exact previous bytes as its adjacent `.bak`; a
|
|
duplicate-only import leaves both target and backup untouched.
|
|
- `nocal --export DESTINATION --calendar SOURCE` validates the source and
|
|
atomically creates a canonical calendar. It never overwrites an existing
|
|
destination and refuses any source whose events are invalid or that cannot
|
|
be safely round-tripped.
|
|
|
|
The two operations are mutually exclusive and cannot be combined with demo,
|
|
frame printing, or backup restoration. These restrictions keep destructive
|
|
ambiguity out of automation and make every file-writing mode deliberate.
|
|
|
|
## 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.
|