oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1# Product
2
3<!-- impeccable:product-schema 1 -->
4
5## Platform
6
7web
8
9## Users
10
11**Not yet confirmed by Paul.** Written from the suite's own pattern —
12docs and sheets both ruled the same way on 2026-09-01 — and flagged here
13rather than presented as settled.
14
15Paul and his collaborators, dogfooding. Real use, small N. Design for
16the people actually in it rather than a hypothesised market: a feature
17nobody here uses is not evidence of anything.
18
19They are running the CARLOS suite already, so Calendar is not entered
20cold. A person arrives with a session and the calendar is one of several
21apps on one instance.
22
23Beyond the team there is one further audience, and it is not a person:
24a **subscribed calendar client** — Google, Apple, Thunderbird — reading
25a feed URL. It cannot sign in, it polls forever, and it is the only
26consumer of this app that will never see a page. The export surface is
27designed for it rather than as an afterthought to the UI.
28
29Guests and invitations do not exist yet. That is a deferral with a
30written trigger, not an oversight — see AGENTS.md.
31
32## Product Purpose
33
34A calendar for one team, on that team's own instance, whose events are
35sealed at rest.
36
37Success is that a team can run its actual week — see the day, add
38something, be reminded, and subscribe from the calendar app they already
39use — without the storage layer ever holding a plaintext title, and
40without the product lying to them about what that does and does not
41protect.
42
43## Positioning
44
45Four claims a neighbouring product could not truthfully copy:
46
471. **Sealed at rest, honestly scoped.** The bucket never holds a
48 plaintext title, place or note. The instance holds the key while it
49 runs, so a compromised live host *can* read a calendar — and the
50 README says so. **We never claim "we can't read your data."**
51
52 The honest scoping matters more here than in sheets, because a
53 calendar's plaintext shape is *free/busy*: publish this database and
54 the whole team's availability is legible even though no event's
55 content is. That is a bigger leak than "the size of every sheet", and
56 AGENTS.md says so in those words rather than letting the rule be read
57 as covering it.
58
592. **No affordance is script-required — including the grid.** Overlapping
60 events in a week view are normally packed by JavaScript measuring
61 boxes in a browser. Here the server does it, and click-a-time-to-
62 create is 48 ordinary links per day. The scriptless page is the
63 product, not the fallback.
64
653. **The record is what you said, not when it happens.** An event is a
66 wall-clock time plus a zone; the UTC instant is a derived index. A
67 tzdata update moves the index and leaves the meeting at nine. Most
68 calendars store the instant and cannot answer what was meant.
69
704. **Reminders that survive a machine that is asleep.** The instance
71 hibernates, so an in-process timer is not a scheduler. Three
72 independent paths fire a reminder, and the product states when one
73 arrived late rather than implying it was on time.
74
75## Operating Context
76
77- **One binary per instance**, `CGO_ENABLED=0`, cgo-free SQLite,
78 deployed to CARLOS. Review on a deployed canary, never localhost.
79- **The instance hibernates**, and that is a product constraint rather
80 than an implementation detail: it is why reminders are built the way
81 they are, and why a subscription feed's poll count is shown to the
82 person deciding whether to keep it.
83- Live at `https://calendar.bds.oncarlos.com`; console page at
84 `https://console.carloku.com/a/bds/apps/calendar`.
85
86## The experience this is trying to produce
87
88A calendar's job is not to list events. It is to answer "how is my day"
89— and the feeling a day produces is whether there is room in it.
90
91So **Today opens with a sentence**, not a grid:
92
93> Wednesday. Four things; the first at 09:30, the last ends at 17:00.
94> That leaves two hours and 15 minutes unbooked between them.
95
96and when there is none:
97
98> There is no unbooked hour between them.
99
100which is true, slightly alarming, and the day the person is actually
101having. The arithmetic counts overlapping meetings once and excludes
102notes, because a birthday does not make a Tuesday fuller.
103
104**Three kinds, because a person makes the distinction whether or not the
105software does.** A *meeting* has other people in it and being late
106matters. A *commitment* is time promised to yourself: it fills the day,
107but only you are inconvenienced when it moves. A *note* is true of a day
108without occupying any of it. They differ in reminder defaults, in
109whether they count toward booked hours, and in how they draw.
110
111**The past is written in the past tense.** "Standup, ended 09:30" rather
112than "09:30". A calendar that describes this morning in the future tense
113makes you read the clock to know where you are.
114
115**Deleting a series asks once, and the expensive answer is last with its
116count out loud** — "All 52 of them", never a bare "All".
117
118**Your zone is the headline; another zone is a whisper.** Times are shown
119in the reader's own zone always. Where an event was authored elsewhere,
120that is a quiet line beside it, never the primary.
121
122## Accessibility
123
124WCAG 2.2 AA, no exemptions. 24×24 minimum targets, 320px reflow, colour
125never carrying meaning alone — every chip prints its own time and title,
126cancelled says the word, today has three signals, and the month cell
127prints its date whatever the theme did.
128
129At 320px the month reflows to a stacked list of days with something in
130them. The week keeps its two dimensions and scrolls: a week view IS a
131two-dimensional comparison, and flattening it produces the agenda, which
132already exists and is better at being a list.
133
134## What is deliberately not here
135
136Each of these is a decision, listed so its absence is legible, and each
137has a trigger in AGENTS.md or the brainstorm rather than a vague "later":
138
139attendees and invitations; guests; web push; external feed subscription
140(holidays); VTIMEZONE emission; expanding foreign RRULEs; drag to move
141and resize; search; working-hours shading; free/busy across members;
142attachments; print; "find a time"; anything with "AI" in it.
143
144## Voice
145
146Plain, exact, and never reassuring beyond the facts. Where a feature has
147a cost, the copy states it in the same breath — the share page names
148both what a subscription link gives away and what it costs to run before
149offering the button, and the import page says what will not come across
150before the upload rather than after.
151