feat(documents): calendar view with appointment-style document rows #386

Open
opened 2026-05-03 09:02:53 +02:00 by marcel · 10 comments
Owner

Context

The list view shows documents in reverse-chronological order but gives no sense of how correspondence was distributed across days and months. A calendar grid surfaces this temporal structure and makes it easy to spot dense periods or quiet stretches.

User Story

As a family member, I want to view documents laid out in a monthly calendar grid, so that I can see which days had letters and navigate month by month through the archive.

Acceptance Criteria

Given the /documents page
Then a view toggle is visible allowing the user to switch between List view and Calendar view

Given calendar view is active and a month is displayed
Then each day with documents shows one text row per document
And each row contains: document title, sender name, receiver name(s)
And if a document has more than one receiver, the first is shown followed by ""
And multiple documents on the same day are stacked vertically within the cell

Given the user hovers over a document row in a calendar cell
Then a preview card appears showing the same information as the list view entry
  (title, sender, all receivers, document date, file preview thumbnail if available)

Given the user clicks a document row in a calendar cell
Then they are navigated to /documents/{id}

Given previous/next month navigation buttons are present
When the user clicks them
Then the calendar advances or retreats one month

Given a year/month selector control
When the user selects a year and month
Then the calendar displays that month directly

Given active person or tag filters
Then only documents matching those filters appear in the calendar cells

Given a document has no documentDate set
Then it is not shown in the calendar grid

Backend Note

Month navigation requires fetching documents scoped to year + month — either via new query parameters on the existing search endpoint or a dedicated endpoint. The current offset-based pagination does not apply here; one calendar page = one calendar month.

Open Questions

  • OQ-3: When the user first opens calendar view, which month is shown — current calendar month, month of the most recent document, or the month set by any active date range filter?
  • OQ-4: When a day cell has many documents (e.g. 10+), does the cell scroll, or is there a "N more" overflow indicator with click-to-expand?
## Context The list view shows documents in reverse-chronological order but gives no sense of how correspondence was distributed across days and months. A calendar grid surfaces this temporal structure and makes it easy to spot dense periods or quiet stretches. ## User Story As a family member, I want to view documents laid out in a monthly calendar grid, so that I can see which days had letters and navigate month by month through the archive. ## Acceptance Criteria ```gherkin Given the /documents page Then a view toggle is visible allowing the user to switch between List view and Calendar view Given calendar view is active and a month is displayed Then each day with documents shows one text row per document And each row contains: document title, sender name, receiver name(s) And if a document has more than one receiver, the first is shown followed by "…" And multiple documents on the same day are stacked vertically within the cell Given the user hovers over a document row in a calendar cell Then a preview card appears showing the same information as the list view entry (title, sender, all receivers, document date, file preview thumbnail if available) Given the user clicks a document row in a calendar cell Then they are navigated to /documents/{id} Given previous/next month navigation buttons are present When the user clicks them Then the calendar advances or retreats one month Given a year/month selector control When the user selects a year and month Then the calendar displays that month directly Given active person or tag filters Then only documents matching those filters appear in the calendar cells Given a document has no documentDate set Then it is not shown in the calendar grid ``` ## Backend Note Month navigation requires fetching documents scoped to `year + month` — either via new query parameters on the existing search endpoint or a dedicated endpoint. The current offset-based pagination does not apply here; one calendar page = one calendar month. ## Open Questions - **OQ-3:** When the user first opens calendar view, which month is shown — current calendar month, month of the most recent document, or the month set by any active date range filter? - **OQ-4:** When a day cell has many documents (e.g. 10+), does the cell scroll, or is there a "N more" overflow indicator with click-to-expand?
marcel added this to the Reader Experience v1 milestone 2026-05-03 09:02:53 +02:00
marcel added the P2-mediumfeatureui labels 2026-05-03 09:03:04 +02:00
Author
Owner

q3: Year and month of the earliest document
q4: yes, overflow indicator

q3: Year and month of the earliest document q4: yes, overflow indicator
Author
Owner

🏛️ Markus Keller — Application Architect

Observations

  • The issue correctly identifies that the existing /api/documents/search endpoint's offset-based pagination doesn't fit calendar view — a calendar page is a month boundary, not a page offset. This is the right problem to name.
  • OQ-3 is now answered (earliest document's year and month), OQ-4 is now answered (overflow indicator). Neither resolution introduces architectural concerns.
  • The existing isBetween(from, to) specification in DocumentSpecifications can serve the calendar view directly: pass from = first day of month, to = last day of month. No new backend infrastructure is required — this is the simplest possible approach.
  • The issue's "Backend Note" mentions "new query parameters or a dedicated endpoint." The existing ?from=&to= parameters on /api/documents/search already do the job. A dedicated endpoint would duplicate the specification chain for no architectural gain.
  • The hover-preview card described in the acceptance criteria implies client-side state (hover target, preview card positioning). This is a UI concern, not a transport concern — SSR data is already loaded, no extra API call needed for the preview.
  • A year/month URL parameter pair (e.g. ?view=calendar&year=1923&month=03) is the correct approach to make calendar state bookmarkable and shareable. This should live in the SvelteKit route's URL, not in component state.
  • The +page.svelte and +page.server.ts for /documents are already 293 and 104 lines respectively. Adding a calendar toggle will push these further. A CalendarView.svelte component that owns all calendar-specific state belongs in src/lib/document/.

Recommendations

  • Reuse the existing search endpoint. Pass from=YYYY-MM-01&to=YYYY-MM-{lastDay} via the existing ?from and ?to parameters. Do not add a dedicated /api/documents/calendar endpoint — that's duplication of a specification chain that already works.
  • Store calendar state in the URL. Add ?view=calendar&year=1923&month=3 as query parameters. The +page.server.ts load function reads them and passes from/to to the existing search endpoint. Bookmarkable, SSR-compatible, and shareable.
  • Determine initial month on the server. To show "earliest document's year and month" on first open, add a DocumentService.findEarliestDocumentDate() method that runs SELECT MIN(document_date) FROM documents WHERE document_date IS NOT NULL. Call it only when no year/month param is present in the URL. Cache the result in the load response; do not make it a separate client-side fetch.
  • Create CalendarView.svelte in src/lib/document/. The monthly grid, cell overflow logic, and hover-preview are a visual unit that should be encapsulated separately from the existing DocumentList.svelte. The /documents/+page.svelte orchestrator selects which view to render based on the view URL param.
  • Update CLAUDE.md route table to document that /documents now accepts ?view=calendar.
  • Add findEarliestDocumentDate() to DocumentService as a plain (non-@Transactional) read method returning Optional<LocalDate>.

Open Decisions

  • The overflow indicator for "N more documents" in a dense cell: should clicking it expand inline (growing the cell height) or open a modal/sheet? The issue says "overflow indicator with click-to-expand" — expanding inline is simpler and avoids a modal dependency, but the maximum cell height in a month with 10+ documents on one day could be significant. Worth deciding before implementation to avoid a rebuild of the cell component.
## 🏛️ Markus Keller — Application Architect ### Observations - The issue correctly identifies that the existing `/api/documents/search` endpoint's offset-based pagination doesn't fit calendar view — a calendar page is a month boundary, not a page offset. This is the right problem to name. - OQ-3 is now answered (earliest document's year and month), OQ-4 is now answered (overflow indicator). Neither resolution introduces architectural concerns. - The existing `isBetween(from, to)` specification in `DocumentSpecifications` can serve the calendar view directly: pass `from = first day of month`, `to = last day of month`. No new backend infrastructure is required — this is the simplest possible approach. - The issue's "Backend Note" mentions "new query parameters or a dedicated endpoint." The existing `?from=&to=` parameters on `/api/documents/search` already do the job. A dedicated endpoint would duplicate the specification chain for no architectural gain. - The hover-preview card described in the acceptance criteria implies client-side state (hover target, preview card positioning). This is a UI concern, not a transport concern — SSR data is already loaded, no extra API call needed for the preview. - A `year/month` URL parameter pair (e.g. `?view=calendar&year=1923&month=03`) is the correct approach to make calendar state bookmarkable and shareable. This should live in the SvelteKit route's URL, not in component state. - The `+page.svelte` and `+page.server.ts` for `/documents` are already 293 and 104 lines respectively. Adding a calendar toggle will push these further. A `CalendarView.svelte` component that owns all calendar-specific state belongs in `src/lib/document/`. ### Recommendations - **Reuse the existing search endpoint.** Pass `from=YYYY-MM-01&to=YYYY-MM-{lastDay}` via the existing `?from` and `?to` parameters. Do not add a dedicated `/api/documents/calendar` endpoint — that's duplication of a specification chain that already works. - **Store calendar state in the URL.** Add `?view=calendar&year=1923&month=3` as query parameters. The `+page.server.ts` load function reads them and passes `from`/`to` to the existing search endpoint. Bookmarkable, SSR-compatible, and shareable. - **Determine initial month on the server.** To show "earliest document's year and month" on first open, add a `DocumentService.findEarliestDocumentDate()` method that runs `SELECT MIN(document_date) FROM documents WHERE document_date IS NOT NULL`. Call it only when no `year`/`month` param is present in the URL. Cache the result in the load response; do not make it a separate client-side fetch. - **Create `CalendarView.svelte` in `src/lib/document/`.** The monthly grid, cell overflow logic, and hover-preview are a visual unit that should be encapsulated separately from the existing `DocumentList.svelte`. The `/documents/+page.svelte` orchestrator selects which view to render based on the `view` URL param. - **Update `CLAUDE.md` route table** to document that `/documents` now accepts `?view=calendar`. - **Add `findEarliestDocumentDate()` to `DocumentService`** as a plain (non-`@Transactional`) read method returning `Optional<LocalDate>`. ### Open Decisions - The overflow indicator for "N more documents" in a dense cell: should clicking it expand inline (growing the cell height) or open a modal/sheet? The issue says "overflow indicator with click-to-expand" — expanding inline is simpler and avoids a modal dependency, but the maximum cell height in a month with 10+ documents on one day could be significant. Worth deciding before implementation to avoid a rebuild of the cell component.
Author
Owner

👨‍💻 Felix Brandt — Fullstack Developer

Observations

  • The existing /api/documents/search endpoint already accepts ?from and ?to as LocalDate parameters fed through isBetween() in DocumentSpecifications. Passing first and last day of the target month is all the backend needs — no new endpoint or migration required.
  • DocumentSearchItem wraps the full Document entity (including sender, receivers, documentDate, thumbnailKey). The calendar cell rows and hover-preview card can be composed entirely from the data that the existing search result already returns.
  • DocumentList.svelte already groups by year using SvelteMap — a pattern that transfers cleanly to a calendar grid that groups by day-of-month.
  • The hover-preview requirement calls for the same information as the list view entry. That content lives in DocumentRow.svelte already. Wrapping it in a hover-triggered overlay is a UI composition problem, not a new data problem.
  • The +page.svelte for /documents is 293 lines. A calendar view adds a month navigation state machine, year/month selectors, and a full grid layout. Those need their own components, not inline markup.

Recommendations

  • Backend: add year and month query params to the existing search endpoint. In DocumentController.search(), accept two new optional @RequestParam Integer year and @RequestParam Integer month. When both are present, compute from = LocalDate.of(year, month, 1) and to = from.withDayOfMonth(from.lengthOfMonth()) and pass them to buildSearchSpec alongside the existing from/to. This avoids a new endpoint and keeps the spec chain as the single source of truth.
  • Add findEarliestDocumentDate() to DocumentService: return documentRepository.findFirstByDocumentDateIsNotNullOrderByDocumentDateAsc().map(Document::getDocumentDate). Add the derived query to DocumentRepository. No new SQL needed.
  • Frontend: split into two components. CalendarGrid.svelte owns the month grid (7-column CSS grid), CalendarCell.svelte owns one day cell including the document rows and overflow indicator. Both go in src/lib/document/. The /documents/+page.svelte switches between <DocumentList> and <CalendarGrid> based on data.view.
  • Preview card: use a CSS-positioned <div> triggered by onmouseenter/onmouseleave on the document row within the cell. Store the hovered document's ID in a $state variable on CalendarCell. Render the preview inline in the cell using position: absolute; z-index: 50. No portal, no external library.
  • Overflow indicator: when a cell has more than a threshold (e.g. 3) documents, show the first N and a +{rest} weitere text button. Clicking toggles a $state showAll flag on that cell — inline expansion, no modal.
  • Keyed {#each} blocks everywhere: {#each days as day (day.iso)}, {#each cellDocs as item (item.document.id)}. Do not leave any list unkeyed.
  • $derived for month boundaries: const firstDay = $derived(new Date(data.year + '-' + pad(data.month) + '-01T12:00:00')). Append T12:00:00 to avoid timezone off-by-one (per project CONTRIBUTING.md date handling rule).
  • After any backend endpoint or DTO change, run npm run generate:api before writing frontend code — the new year/month params must appear in the generated types.

Open Decisions

  • None — all decisions above have clear winners given existing patterns in the codebase.
## 👨‍💻 Felix Brandt — Fullstack Developer ### Observations - The existing `/api/documents/search` endpoint already accepts `?from` and `?to` as `LocalDate` parameters fed through `isBetween()` in `DocumentSpecifications`. Passing first and last day of the target month is all the backend needs — no new endpoint or migration required. - `DocumentSearchItem` wraps the full `Document` entity (including `sender`, `receivers`, `documentDate`, `thumbnailKey`). The calendar cell rows and hover-preview card can be composed entirely from the data that the existing search result already returns. - `DocumentList.svelte` already groups by year using `SvelteMap` — a pattern that transfers cleanly to a calendar grid that groups by day-of-month. - The hover-preview requirement calls for the same information as the list view entry. That content lives in `DocumentRow.svelte` already. Wrapping it in a hover-triggered overlay is a UI composition problem, not a new data problem. - The `+page.svelte` for `/documents` is 293 lines. A calendar view adds a month navigation state machine, year/month selectors, and a full grid layout. Those need their own components, not inline markup. ### Recommendations - **Backend: add `year` and `month` query params to the existing search endpoint.** In `DocumentController.search()`, accept two new optional `@RequestParam Integer year` and `@RequestParam Integer month`. When both are present, compute `from = LocalDate.of(year, month, 1)` and `to = from.withDayOfMonth(from.lengthOfMonth())` and pass them to `buildSearchSpec` alongside the existing `from`/`to`. This avoids a new endpoint and keeps the spec chain as the single source of truth. - **Add `findEarliestDocumentDate()` to `DocumentService`**: `return documentRepository.findFirstByDocumentDateIsNotNullOrderByDocumentDateAsc().map(Document::getDocumentDate)`. Add the derived query to `DocumentRepository`. No new SQL needed. - **Frontend: split into two components.** `CalendarGrid.svelte` owns the month grid (7-column CSS grid), `CalendarCell.svelte` owns one day cell including the document rows and overflow indicator. Both go in `src/lib/document/`. The `/documents/+page.svelte` switches between `<DocumentList>` and `<CalendarGrid>` based on `data.view`. - **Preview card: use a CSS-positioned `<div>` triggered by `onmouseenter`/`onmouseleave`** on the document row within the cell. Store the hovered document's ID in a `$state` variable on `CalendarCell`. Render the preview inline in the cell using `position: absolute; z-index: 50`. No portal, no external library. - **Overflow indicator:** when a cell has more than a threshold (e.g. 3) documents, show the first N and a `+{rest} weitere` text button. Clicking toggles a `$state showAll` flag on that cell — inline expansion, no modal. - **Keyed `{#each}` blocks everywhere:** `{#each days as day (day.iso)}`, `{#each cellDocs as item (item.document.id)}`. Do not leave any list unkeyed. - **`$derived` for month boundaries:** `const firstDay = $derived(new Date(data.year + '-' + pad(data.month) + '-01T12:00:00'))`. Append `T12:00:00` to avoid timezone off-by-one (per project CONTRIBUTING.md date handling rule). - **After any backend endpoint or DTO change, run `npm run generate:api`** before writing frontend code — the new `year`/`month` params must appear in the generated types. ### Open Decisions - None — all decisions above have clear winners given existing patterns in the codebase.
Author
Owner

🔧 Tobias Wendt — DevOps & Platform Engineer

Observations

  • This feature is entirely application-layer: new frontend components, one or two new backend parameters. No new Docker service, no new volume, no new infrastructure dependency. The platform cost is zero.
  • The "find earliest document" query runs on the documents table against the meta_date column. At ~1500–5000 documents and the bounded family-archive scale, a full table scan for MIN(meta_date) is fast and requires no additional index. The column is already used in sort operations.
  • The hover-preview calls for a file thumbnail. The existing thumbnail flow uses private, max-age=31536000, immutable cache headers with a ?v= cache-busting param tied to thumbnailGeneratedAt. The calendar preview card can reuse those URLs directly — no new caching policy needed.
  • The calendar view changes what /documents returns for a single month rather than a page of 50. Response size per request will likely be smaller than the current 50-document page (most months in a 1930s family archive have far fewer than 50 letters). No payload concerns.
  • The ?view=calendar&year=1923&month=3 URL pattern is stateless and SSR-compatible — exactly what the SvelteKit Node adapter handles well. No additional session state.

Recommendations

  • No infrastructure changes needed. This feature requires no Docker Compose changes, no new CI steps, and no new environment variables.
  • Consider adding meta_date to the existing composite index if query profiling shows the MIN(meta_date) scan is slow at scale. Given the archive's bounded document count, this is low priority — profile first.
  • The thumbnail cache policy (private, max-age=31536000, immutable) already handles the hover-preview correctly. The calendar can use the same ?v={thumbnailGeneratedAt} cache-busting URL pattern without any backend change.
  • No Renovate or dependency update action needed unless a new npm package (e.g. a date library) is introduced. Prefer existing Date APIs and the project's existing handleDateInput() utilities — zero new dependencies is the right call for a date grid.

Open Decisions

  • None specific to infrastructure.
## 🔧 Tobias Wendt — DevOps & Platform Engineer ### Observations - This feature is entirely application-layer: new frontend components, one or two new backend parameters. No new Docker service, no new volume, no new infrastructure dependency. The platform cost is zero. - The "find earliest document" query runs on the `documents` table against the `meta_date` column. At ~1500–5000 documents and the bounded family-archive scale, a full table scan for `MIN(meta_date)` is fast and requires no additional index. The column is already used in sort operations. - The hover-preview calls for a file thumbnail. The existing thumbnail flow uses `private, max-age=31536000, immutable` cache headers with a `?v=` cache-busting param tied to `thumbnailGeneratedAt`. The calendar preview card can reuse those URLs directly — no new caching policy needed. - The calendar view changes what `/documents` returns for a single month rather than a page of 50. Response size per request will likely be smaller than the current 50-document page (most months in a 1930s family archive have far fewer than 50 letters). No payload concerns. - The `?view=calendar&year=1923&month=3` URL pattern is stateless and SSR-compatible — exactly what the SvelteKit Node adapter handles well. No additional session state. ### Recommendations - **No infrastructure changes needed.** This feature requires no Docker Compose changes, no new CI steps, and no new environment variables. - **Consider adding `meta_date` to the existing composite index** if query profiling shows the `MIN(meta_date)` scan is slow at scale. Given the archive's bounded document count, this is low priority — profile first. - **The thumbnail cache policy (`private, max-age=31536000, immutable`) already handles the hover-preview correctly.** The calendar can use the same `?v={thumbnailGeneratedAt}` cache-busting URL pattern without any backend change. - **No Renovate or dependency update action needed** unless a new npm package (e.g. a date library) is introduced. Prefer existing `Date` APIs and the project's existing `handleDateInput()` utilities — zero new dependencies is the right call for a date grid. ### Open Decisions - None specific to infrastructure.
Author
Owner

📋 Elicit — Requirements Engineer

Observations

  • The user story and acceptance criteria are well-formed and specific. The Gherkin scenarios cover the main positive paths clearly. OQ-3 and OQ-4 have both been answered in the existing comment, which resolves the most important open ambiguities.
  • Gap: the initial month load for the "no date filters active" case is underspecified for performance. The answer "earliest document's year and month" implies a backend query to find that date. The AC does not specify what happens during that query's loading period — is there a skeleton loader? A spinner? The existing list view has no such cold-start delay because the list always starts at page 0.
  • Gap: the year/month selector control is mentioned but not specified. Is it a native <select> for year, a <select> for month? A combined date picker? An <input type="month">? The acceptance criterion says "a year/month selector control" — that's wireframe-vocabulary ambiguous and could become a UX inconsistency with the rest of the UI.
  • Gap: filter persistence when switching views. The AC specifies that active person/tag filters carry through into the calendar view. But it does not specify: when the user switches FROM calendar TO list view, do the year/month params translate into a ?from=&to= date range filter in the list? Or does switching to list always reset to the last non-calendar filter state? This is a real edge case users will hit.
  • Gap: the "first receiver followed by …" truncation rule is specified for the cell row, but the hover-preview AC says "all receivers." Confirm: is the hover-preview the only place where all receivers are visible, or should there also be a tooltip/alt-text on the truncated cell row?
  • Gap: keyboard navigation. The AC specifies mouse hover for the preview card. No keyboard equivalent is defined. For users who navigate by keyboard (or use the family archive on a tablet with a keyboard cover), there is no way to access the preview card.
  • Missing NFR: loading state during month navigation. Clicking prev/next triggers a server round-trip. What does the calendar show during that navigation? The list view shows navigating.to !== null to display a loading indicator in SearchFilterBar. The same pattern should apply to month navigation.

Recommendations

  • Add an AC for the year/month selector format: "Given the year/month selector, it consists of a year <select> populated with years that have at least one dated document, and a month <select> with the 12 calendar months." This bounds the implementation and avoids a date-picker debate.
  • Add an AC for view-switching filter behavior: "Given the user is in calendar view for March 1923 and switches to list view, then the list view loads with from=1923-03-01 and to=1923-03-31 pre-populated in the date range filter." This is the most intuitive behavior and preserves context.
  • Add a keyboard AC: "Given a document row in a calendar cell receives keyboard focus, then the preview card is shown (equivalent to hover)." This makes the feature accessible without requiring a separate design decision.
  • Add a loading AC: "Given the user clicks prev/next month or changes the year/month selector, then the calendar grid shows a loading state (skeleton or spinner) while the new month's documents are fetched."
  • Clarify the truncated receiver row: specify whether the first-receiver-plus-ellipsis is accompanied by a title attribute or aria-label that lists all receivers for accessibility purposes.

Open Decisions

  • Filter-to-calendar carry-through (the reverse direction): When the user has a list view with active senderId and tag filters and switches to calendar view, the issue says filters carry through. But what year/month should be shown first? The earliest document matching those filters? The last calendar month the user was viewing? This is a minor UX judgment call with no wrong answer, but it should be decided before implementation to avoid a rebuild of the initial-month logic.
## 📋 Elicit — Requirements Engineer ### Observations - The user story and acceptance criteria are well-formed and specific. The Gherkin scenarios cover the main positive paths clearly. OQ-3 and OQ-4 have both been answered in the existing comment, which resolves the most important open ambiguities. - **Gap: the initial month load for the "no date filters active" case is underspecified for performance.** The answer "earliest document's year and month" implies a backend query to find that date. The AC does not specify what happens during that query's loading period — is there a skeleton loader? A spinner? The existing list view has no such cold-start delay because the list always starts at page 0. - **Gap: the year/month selector control is mentioned but not specified.** Is it a native `<select>` for year, a `<select>` for month? A combined date picker? An `<input type="month">`? The acceptance criterion says "a year/month selector control" — that's wireframe-vocabulary ambiguous and could become a UX inconsistency with the rest of the UI. - **Gap: filter persistence when switching views.** The AC specifies that active person/tag filters carry through into the calendar view. But it does not specify: when the user switches FROM calendar TO list view, do the `year`/`month` params translate into a `?from=&to=` date range filter in the list? Or does switching to list always reset to the last non-calendar filter state? This is a real edge case users will hit. - **Gap: the "first receiver followed by …" truncation rule** is specified for the cell row, but the hover-preview AC says "all receivers." Confirm: is the hover-preview the only place where all receivers are visible, or should there also be a tooltip/alt-text on the truncated cell row? - **Gap: keyboard navigation.** The AC specifies mouse hover for the preview card. No keyboard equivalent is defined. For users who navigate by keyboard (or use the family archive on a tablet with a keyboard cover), there is no way to access the preview card. - **Missing NFR: loading state during month navigation.** Clicking prev/next triggers a server round-trip. What does the calendar show during that navigation? The list view shows `navigating.to !== null` to display a loading indicator in `SearchFilterBar`. The same pattern should apply to month navigation. ### Recommendations - **Add an AC for the year/month selector format:** "Given the year/month selector, it consists of a year `<select>` populated with years that have at least one dated document, and a month `<select>` with the 12 calendar months." This bounds the implementation and avoids a date-picker debate. - **Add an AC for view-switching filter behavior:** "Given the user is in calendar view for March 1923 and switches to list view, then the list view loads with `from=1923-03-01` and `to=1923-03-31` pre-populated in the date range filter." This is the most intuitive behavior and preserves context. - **Add a keyboard AC:** "Given a document row in a calendar cell receives keyboard focus, then the preview card is shown (equivalent to hover)." This makes the feature accessible without requiring a separate design decision. - **Add a loading AC:** "Given the user clicks prev/next month or changes the year/month selector, then the calendar grid shows a loading state (skeleton or spinner) while the new month's documents are fetched." - **Clarify the truncated receiver row:** specify whether the first-receiver-plus-ellipsis is accompanied by a `title` attribute or `aria-label` that lists all receivers for accessibility purposes. ### Open Decisions - **Filter-to-calendar carry-through (the reverse direction):** When the user has a list view with active `senderId` and `tag` filters and switches to calendar view, the issue says filters carry through. But what year/month should be shown first? The earliest document matching those filters? The last calendar month the user was viewing? This is a minor UX judgment call with no wrong answer, but it should be decided before implementation to avoid a rebuild of the initial-month logic.
Author
Owner

🔒 Nora "NullX" Steiner — Security Engineer

Observations

  • The calendar view is a read-only data presentation feature. The data it displays (document title, sender, receivers, thumbnail) already flows through the authenticated session — the same Spring Security filter chain that protects every existing /api/documents/search call protects this one too.
  • The new year and month query parameters are Integer values passed to LocalDate.of(year, month, 1). This constructor throws DateTimeException if the values are out of range (e.g. month=13, year=-9999). The controller must catch this and return a 400, not a 500.
  • The hover-preview renders document title, sender name, and receiver names from server-provided data. These are already stored as plain text in the database and are not rendered as HTML — no XSS surface if rendered via Svelte's default text interpolation ({doc.title} not {@html ...}).
  • Thumbnail URLs are fetched with private cache headers — they are not publicly cacheable, which is correct. The calendar view reuses the same thumbnail URL pattern, so no new cache exposure.
  • The year/month parameters, if not validated, could be passed as very large integers causing integer overflow in LocalDate.of(). Java's LocalDate.of(year, month, day) throws DateTimeException for out-of-range inputs, but the exception should be mapped to a 400 by the GlobalExceptionHandler, not a 500.

Recommendations

  • Add @Min(1) / @Max(9999) validation on year and @Min(1) / @Max(12) on month in the controller (using Bean Validation alongside the existing @Min/@Max on page and size). This prevents any unexpected exception surface from LocalDate.of() and gives the client a clean 400 response.
  • In GlobalExceptionHandler, add a handler for DateTimeException that maps it to a 400 with a structured ErrorCode. This is defensive coverage for any path where invalid date arithmetic reaches the JDK layer.
  • Do not use {@html ...} in any calendar cell or preview card. All document metadata is plain text — render it with standard Svelte interpolation. If summary/transcription snippets with search highlights (the \x01/\x02 delimiter pattern used in search match data) are shown in the preview, use the existing highlight renderer rather than raw HTML injection.
  • The @RequirePermission annotation is not needed on GET /api/documents/search (it is already public-read behind the session auth wall), and the year/month extension of that endpoint inherits the same protection. Confirm there is no permitAll() on this path in SecurityConfig.
  • Write a @WebMvcTest test asserting that ?year=0&month=13 returns 400, not 500. This is a one-line addition to DocumentControllerTest and permanently documents the validation contract.

Open Decisions

  • None — all security concerns above have unambiguous remediation paths.
## 🔒 Nora "NullX" Steiner — Security Engineer ### Observations - The calendar view is a read-only data presentation feature. The data it displays (document title, sender, receivers, thumbnail) already flows through the authenticated session — the same Spring Security filter chain that protects every existing `/api/documents/search` call protects this one too. - The new `year` and `month` query parameters are `Integer` values passed to `LocalDate.of(year, month, 1)`. This constructor throws `DateTimeException` if the values are out of range (e.g. month=13, year=-9999). The controller must catch this and return a 400, not a 500. - The hover-preview renders document title, sender name, and receiver names from server-provided data. These are already stored as plain text in the database and are not rendered as HTML — no XSS surface if rendered via Svelte's default text interpolation (`{doc.title}` not `{@html ...}`). - Thumbnail URLs are fetched with `private` cache headers — they are not publicly cacheable, which is correct. The calendar view reuses the same thumbnail URL pattern, so no new cache exposure. - The `year`/`month` parameters, if not validated, could be passed as very large integers causing integer overflow in `LocalDate.of()`. Java's `LocalDate.of(year, month, day)` throws `DateTimeException` for out-of-range inputs, but the exception should be mapped to a 400 by the `GlobalExceptionHandler`, not a 500. ### Recommendations - **Add `@Min(1)` / `@Max(9999)` validation on `year` and `@Min(1)` / `@Max(12)` on `month` in the controller** (using Bean Validation alongside the existing `@Min`/`@Max` on `page` and `size`). This prevents any unexpected exception surface from `LocalDate.of()` and gives the client a clean 400 response. - **In `GlobalExceptionHandler`, add a handler for `DateTimeException`** that maps it to a 400 with a structured `ErrorCode`. This is defensive coverage for any path where invalid date arithmetic reaches the JDK layer. - **Do not use `{@html ...}` in any calendar cell or preview card.** All document metadata is plain text — render it with standard Svelte interpolation. If summary/transcription snippets with search highlights (the `\x01/\x02` delimiter pattern used in search match data) are shown in the preview, use the existing highlight renderer rather than raw HTML injection. - **The `@RequirePermission` annotation is not needed on `GET /api/documents/search`** (it is already public-read behind the session auth wall), and the year/month extension of that endpoint inherits the same protection. Confirm there is no `permitAll()` on this path in `SecurityConfig`. - **Write a `@WebMvcTest` test** asserting that `?year=0&month=13` returns 400, not 500. This is a one-line addition to `DocumentControllerTest` and permanently documents the validation contract. ### Open Decisions - None — all security concerns above have unambiguous remediation paths.
Author
Owner

🧪 Sara Holt — QA Engineer

Observations

  • The existing DocumentControllerTest and DocumentService test suite cover the ?from/?to date range filter path. The new year/month params translate to from/to before reaching the spec — the translation logic is the new unit under test, not the spec itself.
  • The "earliest document's year/month" initial load involves a findEarliestDocumentDate() call. This needs an integration test against a real PostgreSQL container (via Testcontainers) with a known dataset including one document with no documentDate — to verify the WHERE document_date IS NOT NULL guard works.
  • The overflow indicator ("N more") involves a purely frontend threshold. This is a component behavior that belongs in a vitest-browser-svelte test: render CalendarCell with 5 documents, assert only the first 3 are visible, assert the "2 more" button exists, click it, assert all 5 are visible.
  • The hover-preview card is a component behavior. Test: render CalendarCell with one document item, assert preview is not visible, fire mouseenter on the document row, assert preview becomes visible, assert it contains the document title and sender name.
  • The "document has no documentDate → not shown in calendar" AC is a filter contract. This is a DocumentService unit test: mock the repository to return a document with documentDate = null, assert it does not appear when the month filter is applied. In practice this is enforced by the isBetween spec returning a null predicate for null dates, so the test confirms the spec chain ignores them.
  • Month navigation (prev/next) changes the ?year and ?month URL params. This needs a Playwright E2E test: navigate to /documents?view=calendar&year=1923&month=3, click "next month", assert URL becomes ...month=4, assert the grid shows April 1923 headers.

Recommendations

  • Backend unit test: DocumentControllerTest — assert GET /api/documents/search?year=1923&month=3 returns 200 and that the from/to dates passed to the service are 1923-03-01 and 1923-03-31.
  • Backend unit test: assert GET /api/documents/search?year=0&month=13 returns 400 (covers Nora's validation recommendation).
  • Backend integration test: DocumentServiceIntegrationTest (Testcontainers) — seed one document with documentDate = null and two with dates in March 1923, call searchDocuments(year=1923, month=3, ...), assert exactly two results.
  • Frontend component test (vitest-browser-svelte): CalendarCell — overflow: 5 items, threshold 3, assert "2 weitere" button, click, assert all visible.
  • Frontend component test: CalendarCell — hover preview: mouseenter shows preview with correct title and sender; mouseleave hides it.
  • Playwright E2E test: full month navigation flow — prev/next update URL, grid re-renders with correct month header. Cover the empty month case (month with zero documents) — grid should render but all cells are empty, no error state.
  • Load function test (+page.server.ts): import load directly in Vitest, mock the API client to return an empty result for a given month, assert that year and month are present in the returned data object.
  • Accessibility check: add an AxeBuilder pass on the calendar view in the Playwright E2E suite. The grid structure (7-column CSS grid with day-of-week headers) needs <th scope="col"> or equivalent ARIA roles for screen readers.

Open Decisions

  • Threshold for overflow: the issue says "N more" but doesn't specify the threshold. Recommend fixing it at 3 visible rows per cell (configurable via a component prop so tests can pass threshold=1). Without a fixed threshold, tests cannot assert the overflow behavior deterministically.
## 🧪 Sara Holt — QA Engineer ### Observations - The existing `DocumentControllerTest` and `DocumentService` test suite cover the `?from`/`?to` date range filter path. The new `year`/`month` params translate to `from`/`to` before reaching the spec — the translation logic is the new unit under test, not the spec itself. - The "earliest document's year/month" initial load involves a `findEarliestDocumentDate()` call. This needs an integration test against a real PostgreSQL container (via Testcontainers) with a known dataset including one document with no `documentDate` — to verify the `WHERE document_date IS NOT NULL` guard works. - The overflow indicator ("N more") involves a purely frontend threshold. This is a component behavior that belongs in a `vitest-browser-svelte` test: render `CalendarCell` with 5 documents, assert only the first 3 are visible, assert the "2 more" button exists, click it, assert all 5 are visible. - The hover-preview card is a component behavior. Test: render `CalendarCell` with one document item, assert preview is not visible, fire `mouseenter` on the document row, assert preview becomes visible, assert it contains the document title and sender name. - The "document has no documentDate → not shown in calendar" AC is a filter contract. This is a `DocumentService` unit test: mock the repository to return a document with `documentDate = null`, assert it does not appear when the month filter is applied. In practice this is enforced by the `isBetween` spec returning a `null` predicate for `null` dates, so the test confirms the spec chain ignores them. - Month navigation (prev/next) changes the `?year` and `?month` URL params. This needs a Playwright E2E test: navigate to `/documents?view=calendar&year=1923&month=3`, click "next month", assert URL becomes `...month=4`, assert the grid shows April 1923 headers. ### Recommendations - **Backend unit test:** `DocumentControllerTest` — assert `GET /api/documents/search?year=1923&month=3` returns 200 and that the `from`/`to` dates passed to the service are `1923-03-01` and `1923-03-31`. - **Backend unit test:** assert `GET /api/documents/search?year=0&month=13` returns 400 (covers Nora's validation recommendation). - **Backend integration test:** `DocumentServiceIntegrationTest` (Testcontainers) — seed one document with `documentDate = null` and two with dates in March 1923, call `searchDocuments(year=1923, month=3, ...)`, assert exactly two results. - **Frontend component test (vitest-browser-svelte):** `CalendarCell` — overflow: 5 items, threshold 3, assert "2 weitere" button, click, assert all visible. - **Frontend component test:** `CalendarCell` — hover preview: `mouseenter` shows preview with correct title and sender; `mouseleave` hides it. - **Playwright E2E test:** full month navigation flow — prev/next update URL, grid re-renders with correct month header. Cover the empty month case (month with zero documents) — grid should render but all cells are empty, no error state. - **Load function test (`+page.server.ts`):** import `load` directly in Vitest, mock the API client to return an empty result for a given month, assert that `year` and `month` are present in the returned data object. - **Accessibility check:** add an `AxeBuilder` pass on the calendar view in the Playwright E2E suite. The grid structure (7-column CSS grid with day-of-week headers) needs `<th scope="col">` or equivalent ARIA roles for screen readers. ### Open Decisions - **Threshold for overflow:** the issue says "N more" but doesn't specify the threshold. Recommend fixing it at 3 visible rows per cell (configurable via a component prop so tests can pass `threshold=1`). Without a fixed threshold, tests cannot assert the overflow behavior deterministically.
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Observations

  • This feature serves the "reader" persona (the younger family members on phones). Per the project's device split, the read path is Critical for responsiveness. A 7-column calendar grid is inherently a desktop-first layout — on a 320px phone, 7 columns of calendar cells each containing text rows will overflow or become illegible without deliberate mobile handling.
  • The acceptance criteria describe document rows inside calendar cells containing title, sender, and receiver. On desktop at 1440px this is feasible. On a 375px phone, even a single row of text in a narrow cell is at serious risk of truncation to the point of unreadability for the 60+ audience.
  • The hover-preview card requires mouse interaction, which does not exist on touch devices. The AC specifies "hovers over a document row" — this behavior is undefined for mobile. A tap would navigate to the document (per the "clicks" AC), leaving touch users with no way to see the preview without leaving the page.
  • The year/month selector is not visually specified. Given the archive spans 1899–1950, a <select> with ~50 years is usable. A custom carousel or infinite scroll would be over-engineered.
  • The view toggle (List / Calendar) is a new UI control that does not exist in the current /documents page. Its visual treatment needs to match the project's brand pattern. A segmented control using brand-navy for the active state and border-line for the container fits the existing design language.
  • The "first receiver followed by …" truncation pattern produces text like "Oma Raddatz …" inside a narrow cell. The ellipsis must be a real Unicode (U+2026), not three periods, and the cell must use text-overflow: ellipsis with overflow: hidden and white-space: nowrap — otherwise long names wrap and push the cell height.
  • Empty day cells (no documents) must have sufficient height to maintain grid rhythm. Setting a min-h-[3rem] or min-h-[2.5rem] on cells prevents the grid from collapsing unevenly when early or late cells are empty.

Recommendations

  • Mobile layout: replace the 7-column grid with a compact week-strip list on narrow viewports. Below sm (640px), render each week as a horizontal strip with day labels (Mon–Sun) and dot indicators for days with documents. Tapping a dot expands that day's document rows inline. This is the standard mobile calendar pattern (Google Calendar, Apple Calendar) and works at 320px.
  • Touch-tap replaces hover for the preview. On touch devices, the first tap on a document row shows the preview card (as a bottom sheet or inline expansion); a second tap navigates to the document. On desktop, hover shows the preview and click navigates. Implement with ontouchstart / onmouseenter detection — or simply: if the device supports hover (@media (hover: hover)), use hover for preview; if not, use tap-to-expand-then-tap-to-navigate.
  • View toggle: implement as a <div role="group" aria-label="Ansicht wählen"> containing two <button> elements styled as a segmented control. Active state: bg-brand-navy text-white. Inactive: bg-surface text-ink border-line. Minimum 44×44px tap target each (WCAG 2.2).
  • Year/month selector: use two native <select> elements with a <label> each. Year range populated from earliest to latest document year. Month <select> uses full German month names (Januar, Februar, ...). Pair each <select> with a visible <label> — do not rely on placeholder text. This is accessible by default and requires no JavaScript date picker library.
  • Cell text style: document title in font-serif text-sm text-ink, sender/receiver in font-sans text-xs text-ink-2. Use truncate Tailwind class (which sets overflow: hidden; white-space: nowrap; text-overflow: ellipsis) on the title and sender/receiver spans. Minimum cell font size: 12px (font-serif text-xs = 12px — acceptable for metadata, not for body text).
  • Overflow button text: +{n} weitere styled as font-sans text-xs text-primary hover:underline — same pattern as tag overflow in other list views.
  • Day-of-week headers: font-sans text-xs font-bold uppercase tracking-widest text-ink-3 — matches the project's section-title pattern. Use German abbreviations: Mo, Di, Mi, Do, Fr, Sa, So.
  • Today indicator: if the current calendar month is the present month (unlikely for a historical archive but possible), highlight today's cell with a border border-brand-mint ring. Otherwise no today highlight needed.
  • Accessibility — grid semantics: wrap the calendar in <table role="grid"> with <th scope="col"> for day-of-week headers. This is the accessible pattern for calendar widgets (ARIA authoring practices §3.4). Each day cell is <td role="gridcell">. This enables screen-reader navigation by row and column.

Open Decisions

  • Bottom sheet vs inline expansion for touch-device preview: a bottom sheet is more visually polished but requires a portal/overlay component. Inline expansion (the cell grows to show full details) is simpler and matches the overflow indicator pattern. Given the bounded scope of this archive, recommend inline expansion — no portal needed.
  • Navigation arrows vs selector only: the issue specifies both prev/next buttons AND a year/month selector. Both are correct. The question is button placement — above the grid center-aligned, or flanking the month label? Either works; pick one and keep it consistent with the project's nav patterns (the briefwechsel date navigation could be a reference).
## 🎨 Leonie Voss — UI/UX Design Lead ### Observations - This feature serves the "reader" persona (the younger family members on phones). Per the project's device split, the read path is **Critical** for responsiveness. A 7-column calendar grid is inherently a desktop-first layout — on a 320px phone, 7 columns of calendar cells each containing text rows will overflow or become illegible without deliberate mobile handling. - The acceptance criteria describe document rows inside calendar cells containing title, sender, and receiver. On desktop at 1440px this is feasible. On a 375px phone, even a single row of text in a narrow cell is at serious risk of truncation to the point of unreadability for the 60+ audience. - The hover-preview card requires mouse interaction, which does not exist on touch devices. The AC specifies "hovers over a document row" — this behavior is undefined for mobile. A tap would navigate to the document (per the "clicks" AC), leaving touch users with no way to see the preview without leaving the page. - The year/month selector is not visually specified. Given the archive spans 1899–1950, a `<select>` with ~50 years is usable. A custom carousel or infinite scroll would be over-engineered. - The view toggle (List / Calendar) is a new UI control that does not exist in the current `/documents` page. Its visual treatment needs to match the project's brand pattern. A segmented control using `brand-navy` for the active state and `border-line` for the container fits the existing design language. - The "first receiver followed by …" truncation pattern produces text like "Oma Raddatz …" inside a narrow cell. The ellipsis must be a real Unicode `…` (U+2026), not three periods, and the cell must use `text-overflow: ellipsis` with `overflow: hidden` and `white-space: nowrap` — otherwise long names wrap and push the cell height. - Empty day cells (no documents) must have sufficient height to maintain grid rhythm. Setting a `min-h-[3rem]` or `min-h-[2.5rem]` on cells prevents the grid from collapsing unevenly when early or late cells are empty. ### Recommendations - **Mobile layout: replace the 7-column grid with a compact week-strip list on narrow viewports.** Below `sm` (640px), render each week as a horizontal strip with day labels (Mon–Sun) and dot indicators for days with documents. Tapping a dot expands that day's document rows inline. This is the standard mobile calendar pattern (Google Calendar, Apple Calendar) and works at 320px. - **Touch-tap replaces hover for the preview.** On touch devices, the first tap on a document row shows the preview card (as a bottom sheet or inline expansion); a second tap navigates to the document. On desktop, hover shows the preview and click navigates. Implement with `ontouchstart` / `onmouseenter` detection — or simply: if the device supports hover (`@media (hover: hover)`), use hover for preview; if not, use tap-to-expand-then-tap-to-navigate. - **View toggle:** implement as a `<div role="group" aria-label="Ansicht wählen">` containing two `<button>` elements styled as a segmented control. Active state: `bg-brand-navy text-white`. Inactive: `bg-surface text-ink border-line`. Minimum 44×44px tap target each (WCAG 2.2). - **Year/month selector:** use two native `<select>` elements with a `<label>` each. Year range populated from earliest to latest document year. Month `<select>` uses full German month names (`Januar, Februar, ...`). Pair each `<select>` with a visible `<label>` — do not rely on placeholder text. This is accessible by default and requires no JavaScript date picker library. - **Cell text style:** document title in `font-serif text-sm text-ink`, sender/receiver in `font-sans text-xs text-ink-2`. Use `truncate` Tailwind class (which sets `overflow: hidden; white-space: nowrap; text-overflow: ellipsis`) on the title and sender/receiver spans. Minimum cell font size: 12px (font-serif text-xs = 12px — acceptable for metadata, not for body text). - **Overflow button text:** `+{n} weitere` styled as `font-sans text-xs text-primary hover:underline` — same pattern as tag overflow in other list views. - **Day-of-week headers:** `font-sans text-xs font-bold uppercase tracking-widest text-ink-3` — matches the project's section-title pattern. Use German abbreviations: Mo, Di, Mi, Do, Fr, Sa, So. - **Today indicator:** if the current calendar month is the present month (unlikely for a historical archive but possible), highlight today's cell with a `border border-brand-mint` ring. Otherwise no today highlight needed. - **Accessibility — grid semantics:** wrap the calendar in `<table role="grid">` with `<th scope="col">` for day-of-week headers. This is the accessible pattern for calendar widgets (ARIA authoring practices §3.4). Each day cell is `<td role="gridcell">`. This enables screen-reader navigation by row and column. ### Open Decisions - **Bottom sheet vs inline expansion for touch-device preview:** a bottom sheet is more visually polished but requires a portal/overlay component. Inline expansion (the cell grows to show full details) is simpler and matches the overflow indicator pattern. Given the bounded scope of this archive, recommend inline expansion — no portal needed. - **Navigation arrows vs selector only:** the issue specifies both prev/next buttons AND a year/month selector. Both are correct. The question is button placement — above the grid center-aligned, or flanking the month label? Either works; pick one and keep it consistent with the project's nav patterns (the `briefwechsel` date navigation could be a reference).
Author
Owner

🗂️ Decision Queue — Cross-Persona Open Items

Grouped by theme. Each item is a genuine tradeoff needing your judgment before implementation starts.


Theme A: Cell Overflow Behavior

From: Markus, Felix, Sara, Leonie

When a day cell has more documents than the display threshold, what happens when the user clicks the overflow indicator?

  • Option 1 — Inline expansion: the cell grows vertically to show all documents. Simple, no new component. Risk: a cell with 10+ documents on one day becomes very tall and disrupts the grid rhythm.
  • Option 2 — Modal/sheet: a focused overlay shows all documents for that day. Cleaner visual, but requires a modal component. The archive has few days with 10+ documents, so this may be over-engineering.

Sara adds: the threshold number (e.g. 3 visible before overflow) needs to be decided and written into the AC so tests can assert it deterministically. A configurable prop is fine.

Leonie's recommendation: inline expansion, consistent with the overflow pattern elsewhere in the UI.


Theme B: Mobile Calendar Layout

From: Leonie

The 7-column grid is desktop-native. On phones (320–640px), two options:

  • Option 1 — Scaled-down grid: smaller cells, smaller font, accept that mobile is second-class for calendar view. Per the project device split, calendar view is a "reader" feature (phone users), so this may be unacceptable.
  • Option 2 — Alternate mobile layout: below sm (640px), render a week-strip list (day labels + dot indicators for document days, tap to expand). More work, but genuinely usable at 320px and appropriate for the 60+ touch audience.

Decision needed: is a scaled-down (but not redesigned) desktop grid acceptable for mobile, or is a distinct mobile layout required?


Theme C: Filter-to-Calendar Context on Initial Open

From: Elicit

When the user switches to calendar view with active senderId/tag filters in place, what month is shown first?

  • Option 1 — Earliest document matching the current filters: requires a filtered findEarliestDocumentDate(filters) call. Maximally useful.
  • Option 2 — Absolute earliest document (ignoring filters): simpler — one unfiltered MIN(meta_date) query regardless of filter state. May feel wrong if the filtered set has a much later earliest document.
  • Option 3 — Current calendar month (or hardcoded start year): simplest. Least useful.

Elicit's recommendation: Option 1, but it requires a new service method that accepts the full filter spec. Evaluate whether the complexity is worth it for a first version.


Theme D: View-Switch Date Context (Calendar → List)

From: Elicit

When switching FROM calendar view (e.g. March 1923) TO list view:

  • Option 1 — Carry month as date range: populate from=1923-03-01&to=1923-03-31 in the list view. Preserves the user's navigational context.
  • Option 2 — Reset to no date filter: list view opens with no date constraints (the full archive, page 0). Simpler, but loses the user's context.

Recommendation: Option 1. The buildSearchParams function in +page.svelte already constructs URL params from a filter snapshot — adding from/to from the calendar month is a trivial addition.

## 🗂️ Decision Queue — Cross-Persona Open Items Grouped by theme. Each item is a genuine tradeoff needing your judgment before implementation starts. --- ### Theme A: Cell Overflow Behavior **From: Markus, Felix, Sara, Leonie** When a day cell has more documents than the display threshold, what happens when the user clicks the overflow indicator? - **Option 1 — Inline expansion:** the cell grows vertically to show all documents. Simple, no new component. Risk: a cell with 10+ documents on one day becomes very tall and disrupts the grid rhythm. - **Option 2 — Modal/sheet:** a focused overlay shows all documents for that day. Cleaner visual, but requires a modal component. The archive has few days with 10+ documents, so this may be over-engineering. **Sara adds:** the threshold number (e.g. 3 visible before overflow) needs to be decided and written into the AC so tests can assert it deterministically. A configurable prop is fine. **Leonie's recommendation:** inline expansion, consistent with the overflow pattern elsewhere in the UI. --- ### Theme B: Mobile Calendar Layout **From: Leonie** The 7-column grid is desktop-native. On phones (320–640px), two options: - **Option 1 — Scaled-down grid:** smaller cells, smaller font, accept that mobile is second-class for calendar view. Per the project device split, calendar view is a "reader" feature (phone users), so this may be unacceptable. - **Option 2 — Alternate mobile layout:** below `sm` (640px), render a week-strip list (day labels + dot indicators for document days, tap to expand). More work, but genuinely usable at 320px and appropriate for the 60+ touch audience. **Decision needed:** is a scaled-down (but not redesigned) desktop grid acceptable for mobile, or is a distinct mobile layout required? --- ### Theme C: Filter-to-Calendar Context on Initial Open **From: Elicit** When the user switches to calendar view with active `senderId`/`tag` filters in place, what month is shown first? - **Option 1 — Earliest document matching the current filters:** requires a filtered `findEarliestDocumentDate(filters)` call. Maximally useful. - **Option 2 — Absolute earliest document (ignoring filters):** simpler — one unfiltered `MIN(meta_date)` query regardless of filter state. May feel wrong if the filtered set has a much later earliest document. - **Option 3 — Current calendar month (or hardcoded start year):** simplest. Least useful. **Elicit's recommendation:** Option 1, but it requires a new service method that accepts the full filter spec. Evaluate whether the complexity is worth it for a first version. --- ### Theme D: View-Switch Date Context (Calendar → List) **From: Elicit** When switching FROM calendar view (e.g. March 1923) TO list view: - **Option 1 — Carry month as date range:** populate `from=1923-03-01&to=1923-03-31` in the list view. Preserves the user's navigational context. - **Option 2 — Reset to no date filter:** list view opens with no date constraints (the full archive, page 0). Simpler, but loses the user's context. **Recommendation:** Option 1. The `buildSearchParams` function in `+page.svelte` already constructs URL params from a filter snapshot — adding `from`/`to` from the calendar month is a trivial addition.
Author
Owner

🔗 Dependency from #385 — View toggle state needs a decision before implementation

During the #385 developer discussion, we hit a blocker: AC #7 of #385 states "Given the user switches to calendar view, the timeline widget is hidden." This AC cannot be implemented or tested until we know how the list/calendar view toggle is represented.

The decision needed here:

  • Option A — URL param (e.g. ?view=calendar): state survives reload, links are shareable, +page.server.ts can read it, testable at both server and component layer. The timeline in #385 checks the URL param and hides itself.
  • Option B — Local $state: ephemeral, resets on reload. Testable only at the component layer. The toggle state is passed as a prop to the timeline.

Recommendation: URL param (?view=list default, ?view=calendar when toggled). This is consistent with how other filter state is managed on the documents page, makes the view shareable, and gives #385's timeline a clean signal to act on.

Please settle this before either #385 or #386 goes into implementation — both depend on the answer.

## 🔗 Dependency from #385 — View toggle state needs a decision before implementation During the #385 developer discussion, we hit a blocker: **AC #7 of #385** states "Given the user switches to calendar view, the timeline widget is hidden." This AC cannot be implemented or tested until we know how the list/calendar view toggle is represented. **The decision needed here:** - **Option A — URL param** (e.g. `?view=calendar`): state survives reload, links are shareable, `+page.server.ts` can read it, testable at both server and component layer. The timeline in #385 checks the URL param and hides itself. - **Option B — Local `$state`**: ephemeral, resets on reload. Testable only at the component layer. The toggle state is passed as a prop to the timeline. **Recommendation:** URL param (`?view=list` default, `?view=calendar` when toggled). This is consistent with how other filter state is managed on the documents page, makes the view shareable, and gives #385's timeline a clean signal to act on. Please settle this before either #385 or #386 goes into implementation — both depend on the answer.
Sign in to join this conversation.
No Label P2-medium feature ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#386