Milestone 0/1: shell, calendar/psalter/hours scaffold, Prime
Deploy / deploy (push) Failing after 1m8s

Client-side-first PWA (Vite/TS, no backend) per the approved plan: day-
navigable shell listing all 8 hours, Prime fully resolves via the
calendar -> psalter -> ordo pipeline, the other 7 hours are registered
but flagged not-implemented. Sanctoral/temporal calendar data uses a
day -> id indirection layer (saints, easter-offsets, fixed-date-calendar)
so reassigning a feast to a different day is a data edit, not a code
change. Docker (Caddy-serving-static) + Gitea CI workflow scaffolded to
match the eec/drip/bookshop operational pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 15:20:06 -04:00
commit a5dc6502d7
53 changed files with 8634 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import type { HourId } from './hours/types';
import type { LanguageCode } from './psalter/types';
export interface AppState {
date: string; // ISO YYYY-MM-DD
selectedHour: HourId;
/** Exactly one or two entries — see plan: one-language / two-language layout only. */
languages: [LanguageCode] | [LanguageCode, LanguageCode];
}
export function todayIso(): string {
const d = new Date();
const yyyy = d.getFullYear();
const mm = String(d.getMonth() + 1).padStart(2, '0');
const dd = String(d.getDate()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}`;
}
const state: AppState = {
date: todayIso(),
selectedHour: 'prime',
languages: ['en'],
};
type Listener = (state: Readonly<AppState>) => void;
const listeners = new Set<Listener>();
export function getState(): Readonly<AppState> {
return state;
}
export function subscribe(listener: Listener): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
function notify(): void {
for (const listener of listeners) listener(state);
}
export function setDate(date: string): void {
state.date = date;
notify();
}
export function goToToday(): void {
setDate(todayIso());
}
export function shiftDate(days: number): void {
const d = new Date(`${state.date}T00:00:00Z`);
d.setUTCDate(d.getUTCDate() + days);
setDate(d.toISOString().slice(0, 10));
}
export function setSelectedHour(hourId: HourId): void {
state.selectedHour = hourId;
notify();
}
export function setLanguages(languages: AppState['languages']): void {
state.languages = languages;
notify();
}