feat: add safe calendar import and export
Add explicit noninteractive CLI transfer modes with full event validation, idempotent duplicate handling, collision refusal, atomic import backups, and no-overwrite export creation. Cover pure merge semantics and end-to-end rollback paths for unsupported content, invalid identities, conflicts, option combinations, and existing destinations.
This commit is contained in:
@@ -82,6 +82,25 @@ 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.
|
||||
|
||||
Explicit CLI import and export reuse the storage parser, safety classification,
|
||||
validation, and atomic persistence boundary; these workflows do not depend on
|
||||
the TUI and perform no network access. Import loads and validates both source
|
||||
and target before staging a target replacement. UIDs in both files must be
|
||||
non-empty and unique, every interval must be valid, and every component in both
|
||||
files must be safe to round-trip. Matching UID plus identical fields is an
|
||||
idempotent duplicate; matching UID with different fields is a collision that
|
||||
aborts the whole operation. A committed merge writes the exact old target to
|
||||
`.bak` before atomic replacement. If every source event is already present
|
||||
exactly, import performs no write and does not alter the backup.
|
||||
|
||||
Export accepts only a valid, safely round-trippable source, serializes a
|
||||
canonical calendar through private staging, and atomically creates a destination
|
||||
that must not already exist. Destination refusal is part of the
|
||||
commit boundary, so export never replaces an existing path. Import, export,
|
||||
demo, non-interactive frame printing, and backup restoration are distinct
|
||||
top-level application modes; import and export cannot be selected together or
|
||||
combined with any of the other modes.
|
||||
|
||||
The current writer deliberately refuses to mutate an existing file when the
|
||||
loader encounters information it cannot round-trip, including recurrence
|
||||
overrides, `RDATE`, custom `VTIMEZONE` definitions, alarms, attendees, unknown
|
||||
|
||||
@@ -81,7 +81,7 @@ The full terminal is treated as a responsive canvas:
|
||||
|
||||
The first usable foundation supports day and appointment navigation, a
|
||||
full-frame reader, a read-only 42-day agenda, validated add/edit/delete forms,
|
||||
and atomic `.ics` writes.
|
||||
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
|
||||
@@ -101,6 +101,27 @@ agenda. Agenda navigation never persists state, and add/edit/delete remain in
|
||||
the month workflow. The next local-data work adds advanced recurrence overrides
|
||||
and rule editing.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
- Occurrence-aware appointment search with exact result-to-grid navigation
|
||||
- Session calendar toggles shared by month rendering, focus, and search
|
||||
- Read-only 42-day agenda with bounded recurrence and exact return navigation
|
||||
- Explicit atomic import/export with validation, collision refusal, and backups
|
||||
- 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
|
||||
|
||||
- Advanced recurrence editing, RDATE/overrides, and embedded VTIMEZONE support
|
||||
- Import/export and configurable week start
|
||||
- Configurable week start
|
||||
- Screen-reader-friendly linear view and full Unicode display-width handling
|
||||
|
||||
## 0.2 — durable cache and provider boundary
|
||||
|
||||
Reference in New Issue
Block a user