|
| 1 | +# Calendar component extraction — design rationale |
| 2 | + |
| 3 | +This document captures the *why* and *how* of moving `CalendarView` |
| 4 | +from a single-module demo view to a publishable Vaadin Flow add-on |
| 5 | +spanning two artefacts. Written post-hoc with hindsight from running |
| 6 | +all three phases end-to-end. |
| 7 | + |
| 8 | +For status / scope of future evolutions, see |
| 9 | +[`FEATURE_BACKLOG.md`](FEATURE_BACKLOG.md). |
| 10 | + |
| 11 | +## 1. Goal |
| 12 | + |
| 13 | +Originally `CalendarView` was hardwired into this specific project: |
| 14 | +host-specific routing annotations, host-specific i18n interface, |
| 15 | +direct `VaadinSession.getAttribute(...)` for persistence, dependency |
| 16 | +on a static `CalendarServiceProvider` holder, custom CSS in the |
| 17 | +host's `frontend/` tree. Extracting it as a drop-in Vaadin composite |
| 18 | +the rest of the world can `dependency`-pull was the goal. |
| 19 | + |
| 20 | +Target shape: |
| 21 | + |
| 22 | +```xml |
| 23 | +<dependency> |
| 24 | + <groupId>com.svenruppert.vaadin.calendar</groupId> |
| 25 | + <artifactId>calendar-component</artifactId> |
| 26 | + <version>00.10.00</version> |
| 27 | +</dependency> |
| 28 | +``` |
| 29 | + |
| 30 | +Mount it in any Vaadin app with a thin host-side wrapper carrying |
| 31 | +that app's own `@Route` + permission annotations. |
| 32 | + |
| 33 | +## 2. Architecture decisions |
| 34 | + |
| 35 | +Four load-bearing calls Sven took before Phase 1 started (preserved |
| 36 | +in [`memory/project_calendar_component_extraction`](../.claude/projects/.../memory/project_calendar_component_extraction.md)): |
| 37 | + |
| 38 | +| # | Decision | Rationale | |
| 39 | +|---|---|---| |
| 40 | +| **Modules** | Two Maven artefacts: `calendar-caldav` (headless) and `calendar-component` (Vaadin) | Headless CLI / sync-job consumers can pull only the wire layer; the UI tree stays separable. | |
| 41 | +| **Error API** | `Result<T, CalDavError>` stays the public service-boundary type | Already runs through the codebase; `try/catch` is uglier and loses the typed error domain. | |
| 42 | +| **Coordinates** | `com.svenruppert.vaadin.calendar` namespace | Consistent with `com.svenruppert:functional-reactive`, `com.svenruppert:caldav-testbench`. | |
| 43 | +| **Presets** | `CalDavProviderPreset.DEFAULTS` (incl. Apple iCloud) bundled in the artefact | Quick-connect out-of-the-box; consumers can still inject additional presets via the config. | |
| 44 | + |
| 45 | +## 3. Final module shape |
| 46 | + |
| 47 | +``` |
| 48 | +caldav-demo-reactor (pom — reactor root) |
| 49 | +├── calendar-caldav (jar) |
| 50 | +│ └── com.svenruppert.vaadin.calendar |
| 51 | +│ ├── client/ CalDavClient · CalDavDiscovery · CalDavError(s) · RemoteEvent |
| 52 | +│ ├── mapping/ EntryMapper (VEVENT/VTODO ↔ Entry) |
| 53 | +│ ├── service/ CalendarService · CalDavConnectionConfig |
| 54 | +│ │ CalDavServerConnection · CalendarSubscription |
| 55 | +│ │ CalDavProviderPreset |
| 56 | +│ ├── state/ CalendarStateStore (interface) |
| 57 | +│ └── i18n/ CalendarMessages (interface) |
| 58 | +│ |
| 59 | +├── calendar-component (jar — Vaadin Flow add-on) |
| 60 | +│ └── com.svenruppert.vaadin.calendar |
| 61 | +│ ├── ui/ CalendarView · CalendarNavigationBar |
| 62 | +│ │ ConnectionStatusBadge · ConnectionsDialog |
| 63 | +│ │ EventEditorDialog · ServerStatusList |
| 64 | +│ │ SubscriptionsDialog |
| 65 | +│ └── state/ VaadinSessionCalendarStateStore (default impl) |
| 66 | +│ └── META-INF/resources/frontend/styles/calendar-view.css |
| 67 | +│ |
| 68 | +└── demo (war — the consuming application) |
| 69 | + └── com.svenruppert.flow |
| 70 | + ├── … (jSentinel, MainLayout, AppShell, all non-calendar views) |
| 71 | + └── views/CalendarRouteView @Route("calendar"), @VisibleFor(USER), |
| 72 | + layout = MainLayout.class |
| 73 | + ↓ embeds |
| 74 | + new CalendarView(store, messages) |
| 75 | +``` |
| 76 | + |
| 77 | +The host (`demo`) carries every project-specific concern; both |
| 78 | +add-on artefacts are host-neutral. |
| 79 | + |
| 80 | +## 4. Three integration seams |
| 81 | + |
| 82 | +The published `calendar-component` exposes exactly three |
| 83 | +constructor-injected concerns: |
| 84 | + |
| 85 | +### 4.1 `CalendarStateStore` |
| 86 | + |
| 87 | +Persists Connection, Servers, Subscriptions, and the N-days slider |
| 88 | +between navigations. The default |
| 89 | +`VaadinSessionCalendarStateStore` keeps everything on the current |
| 90 | +session (the legacy behaviour). Plug a database-backed impl for |
| 91 | +cross-session continuity, or an in-memory stub for tests. |
| 92 | + |
| 93 | +```java |
| 94 | +public interface CalendarStateStore { |
| 95 | + Optional<CalDavConnectionConfig> readConnection(); |
| 96 | + void writeConnection(CalDavConnectionConfig cfg); |
| 97 | + |
| 98 | + List<CalDavServerConnection> readServers(); |
| 99 | + void writeServers(List<CalDavServerConnection> servers); |
| 100 | + |
| 101 | + List<CalendarSubscription> readSubscriptions(); |
| 102 | + void writeSubscriptions(List<CalendarSubscription> subs); |
| 103 | + |
| 104 | + int readNDays(int fallback); |
| 105 | + void writeNDays(int n); |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +### 4.2 `CalendarMessages` |
| 110 | + |
| 111 | +The i18n seam. A single functional method |
| 112 | +`tr(key, fallback, args...)`. Pass `this::tr` from your host's own |
| 113 | +i18n interface, or `CalendarMessages.fallbackOnly()` for an |
| 114 | +English-only build. |
| 115 | + |
| 116 | +### 4.3 `CalendarService` *(optional)* |
| 117 | + |
| 118 | +A pre-built service instance. Omit and the view bootstraps from the |
| 119 | +store's connection config, falling back to |
| 120 | +`CalDavProviderPreset.DEFAULTS.get(0)` (Apple iCloud quick-connect). |
| 121 | + |
| 122 | +## 5. The phases, retold |
| 123 | + |
| 124 | +### Phase 1 — In-place decouple |
| 125 | + |
| 126 | +Stayed inside the single module. Introduced the |
| 127 | +`CalendarStateStore` and `CalendarMessages` interfaces, threaded |
| 128 | +them through `CalendarView` + every sub-component, moved |
| 129 | +`@Route` / `@VisibleFor` to a new `CalendarRouteView` wrapper. |
| 130 | +Replaced the host's `PageHeader` brick with a local |
| 131 | +`.calendar-view__page-header` div + CSS. Kept |
| 132 | +`CalendarServiceProvider` for the test-injection shim, |
| 133 | +encapsulated in `hostShimmedDefaultService()`. |
| 134 | + |
| 135 | +Verification: 267 → 284 tests (the new abstractions ship with their |
| 136 | +own coverage), all green. SpotBugs 0 findings. |
| 137 | + |
| 138 | +### Phase 2 — Multi-module reactor |
| 139 | + |
| 140 | +Root pom becomes `packaging=pom` reactor. Existing source moves |
| 141 | +into `demo/`. Two new modules `calendar-caldav/` and |
| 142 | +`calendar-component/` get carved out by file-rename + dependency |
| 143 | +plumbing. `CalendarServiceProvider` deleted; the |
| 144 | +`CalendarViewBrowserlessTest` injection mechanism switches from |
| 145 | +`Provider.setService(testService)` to seeding the session |
| 146 | +connection config in `@BeforeEach`. |
| 147 | + |
| 148 | +The split-package gotcha (`com.svenruppert.flow.views.*` lived in |
| 149 | +both demo and calendar-component) was resolved by giving the |
| 150 | +`calendar-component` its own UI package layer |
| 151 | +(`com.svenruppert.vaadin.calendar.ui.*`). |
| 152 | + |
| 153 | +Verification: 66 + 5 + 213 = 284 tests across the reactor, all |
| 154 | +green. SpotBugs 0 each module. |
| 155 | + |
| 156 | +### Phase 3 — Cleanup + namespace + publish prep |
| 157 | + |
| 158 | +Trailing whitespace stripped from the EUPL headers that earlier |
| 159 | +license-plugin runs had baked in; `maven-checkstyle-plugin` |
| 160 | +re-enabled with 0 violations on both add-on modules. |
| 161 | +`license-maven-plugin` stays disabled in the add-on modules — it |
| 162 | +would otherwise re-inject the long header on every build. |
| 163 | + |
| 164 | +The full namespace move from `com.svenruppert.flow.*` to |
| 165 | +`com.svenruppert.vaadin.calendar.*` ran via three `perl -i -pe` |
| 166 | +passes — bulk-rewriting package declarations and imports across |
| 167 | +the two new modules and threading through the demo. The |
| 168 | +`CalendarViewBrowserlessTest` package and physical location |
| 169 | +followed. |
| 170 | + |
| 171 | +Sources + Javadoc jars wired up via explicit |
| 172 | +`maven-source-plugin` + `maven-javadoc-plugin` executions in each |
| 173 | +add-on pom — the publishable triplet now drops into |
| 174 | +`target/` on every `mvn install`. `distributionManagement` |
| 175 | +(s01.oss.sonatype.org) and `maven-gpg-plugin` are inherited from |
| 176 | +the parent `com.svenruppert:dependencies`. |
| 177 | + |
| 178 | +`README.md` for each add-on module covers coordinates, package |
| 179 | +map, minimal mount snippet, and customisation seams. |
| 180 | + |
| 181 | +## 6. What stayed deliberately out of scope |
| 182 | + |
| 183 | +- **Maven Central deploy ceremony** — interactive: Sonatype creds |
| 184 | + + GPG passphrase. The pom is wired; the push is a Sven action. |
| 185 | +- **Per-entry colour + calendar stripe** — parked in |
| 186 | + [`FEATURE_BACKLOG.md`](FEATURE_BACKLOG.md) #1. |
| 187 | +- **Decoupling `EntryMapper` from `org.vaadin.stefan:fullcalendar2`** |
| 188 | + — would make `calendar-caldav` truly transitive-free of Vaadin |
| 189 | + at the cost of an extra DTO layer. Accept the transitive for v1. |
| 190 | +- **Provider-agnostic preset catalogue** — for now `iCloud` ships |
| 191 | + hard-coded in `CalDavProviderPreset.DEFAULTS`; Nextcloud / |
| 192 | + Radicale / Baïkal entries would be drop-in additions to that |
| 193 | + list when the demand surfaces. |
| 194 | +- **CSS-as-code variants** — the bundled CSS is fully themable via |
| 195 | + Lumo custom properties (`--lumo-*`) and the BEM-ish class names |
| 196 | + documented in `calendar-component/README.md`. No Lumo-fork. |
| 197 | + |
| 198 | +## 7. Verification commands |
| 199 | + |
| 200 | +```bash |
| 201 | +./mvnw test # 284 tests across the reactor |
| 202 | +./mvnw -pl <m> spotbugs:check # 0 findings each module |
| 203 | +./mvnw -pl <m> validate # 0 checkstyle violations (add-ons) |
| 204 | +./mvnw -DskipTests install # main + sources + javadoc jars |
| 205 | + # in calendar-{caldav,component}/target/ |
| 206 | +``` |
| 207 | + |
| 208 | +End-to-end smoke: |
| 209 | + |
| 210 | +```bash |
| 211 | +./start-caldav-dev-server.sh # Terminal 1 |
| 212 | +./start-vaadin-demo.sh # Terminal 2 |
| 213 | +# http://localhost:8080/, login → /calendar |
| 214 | +``` |
0 commit comments