docs(research): add smart screen stack, additions, and Nextcloud research
Cover Symfony backend, calendar sync (Exchange Graph API + Mail-in-a-Box CalDAV), Docker/Gitea CI, frontend stack, Diun/RSS/QR additions, and Nextcloud Tasks (CalDAV VTODO) integration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# Smart Tablet Screen — Stack Research
|
||||
|
||||
Research date: 2026-09-01. All claims below are sourced against primary/official documentation, source code, or first-party API references — not third-party summaries. Each claim links its own source.
|
||||
|
||||
---
|
||||
|
||||
## 1. Calendar sync: Mail-in-a-Box (self-hosted accounts)
|
||||
|
||||
- Mail-in-a-Box bundles **Nextcloud** to provide contacts and calendar sync, exposed over **CardDAV/CalDAV**. The project's own repo/site lists "CardDAV/CalDAV (Nextcloud)" among the components it installs, and states each box "includes contacts and calendar synchronization." — [mail-in-a-box/mailinabox GitHub repo](https://github.com/mail-in-a-box/mailinabox), [mailinabox.email](https://mailinabox.email/)
|
||||
- Mail-in-a-Box also bundles **Z-Push**, which provides **Exchange ActiveSync** as an alternative sync protocol for compatible mobile clients — this is a second, separate protocol path, not the CalDAV path. — [mail-in-a-box/mailinabox GitHub repo](https://github.com/mail-in-a-box/mailinabox)
|
||||
- **Conclusion for this project**: point the Symfony backend's CalDAV client at each Mail-in-a-Box user's Nextcloud CalDAV URL (standard Nextcloud path pattern `/remote.php/dav/calendars/<username>/`, per Nextcloud's own admin docs, which Mail-in-a-Box's bundled Nextcloud inherits) — [Nextcloud Calendar/CalDAV Administration Manual](https://docs.nextcloud.com/server/stable/admin_manual/groupware/calendar.html). Do not use Z-Push/ActiveSync; CalDAV is the natively-scriptable, standards-based path and matches what Symfony would consume as a generic CalDAV client (RFC 4791).
|
||||
|
||||
## 2. Calendar sync: Microsoft Exchange/Outlook.com (free account)
|
||||
|
||||
- **Microsoft's own recommended API for calendar access is Microsoft Graph**, not EWS or CalDAV. Graph's Outlook calendar overview explicitly states: *"Most features in the Outlook calendar API apply to calendars in personal Microsoft accounts and work or school accounts"* — confirming free Outlook.com (personal Microsoft account) is a first-class supported account type, not just Microsoft 365/Exchange Online. — [Outlook calendar API overview – Microsoft Graph, learn.microsoft.com](https://learn.microsoft.com/en-us/graph/outlook-calendar-concept-overview)
|
||||
- **EWS is being retired for Exchange Online** with a hard enforcement date of **October 1, 2026**, after which EWS stops working for Microsoft 365 mailboxes; Microsoft's stated replacement is Graph. This retirement is scoped to Exchange Online — it does not directly affect consumer Outlook.com accounts (which were never on the EWS-for-M365 retirement track the same way), but it confirms Graph as Microsoft's forward path and rules out building new EWS integrations. — [Deprecation of Exchange Web Services in Exchange Online, learn.microsoft.com](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-ews-exchange-online)
|
||||
- **Outlook.com/Outlook (new) does not support CalDAV.** This is corroborated by numerous first-party Microsoft Q&A threads (Microsoft's own support forum) confirming CalDAV has never been natively implemented for Outlook.com, and that the "new Outlook" does not support third-party CalDAV add-ins. There is no official Microsoft doc offering a CalDAV endpoint for Outlook.com. — [Microsoft Q&A: CalDAV support on Outlook.com](https://learn.microsoft.com/en-us/answers/questions/4525673/caldav-support-on-outlook-com), [Microsoft Q&A: caldav in new outlook](https://learn.microsoft.com/en-us/answers/questions/2282064/caldav-in-new-outlook)
|
||||
- **Auth**: Graph calendar access on a personal account requires standard OAuth 2.0 authorization-code flow against Microsoft Entra ID (`/authorize` and `/token` endpoints), with an app registered in the Entra portal, a `client_id`, redirect URI, and — for a confidential/web client like a Symfony backend — a `client_secret`. Use `tenant=common` (or `consumers`) so both personal Microsoft accounts and (optionally) work/school accounts can sign in. A refresh token (via `offline_access` scope) is needed for the long-lived, unattended calendar-polling this dashboard needs. — [Get access on behalf of a user – Microsoft Graph, learn.microsoft.com](https://learn.microsoft.com/en-us/graph/auth-v2-user)
|
||||
- **Conclusion for this project**: use **Microsoft Graph's calendar API** (`/me/calendarview`, `/me/events`, delta query for sync) via OAuth 2.0 with a registered Entra app, storing the client id/secret and refresh token via the project's `.env`. This is the only viable, supported, future-proof path — CalDAV is not available and EWS is being sunset.
|
||||
|
||||
## 3. CSS methodology — BEM
|
||||
|
||||
Per BEM's own quick-start guide:
|
||||
- **Block**: "a functionally independent page component that can be reused" — named by purpose, not appearance (e.g. `menu`, `button`); must not set its own external position/margins.
|
||||
- **Element**: "a composite part of a block that can't be used separately from it" — syntax `block-name__element-name` (double underscore).
|
||||
- **Modifier**: defines appearance/state/behavior of a block or element — boolean form `block-name_modifier-name`, key-value form `block-name_modifier-name_value`. Modifiers never exist standalone; they always accompany a block/element.
|
||||
— [BEM Quick Start, bem.info](https://bem.info/en/methodology/quick-start/)
|
||||
|
||||
**Conclusion**: adopt BEM class naming directly as documented (`.calendar`, `.calendar__event`, `.calendar__event_source_exchange`) for all hand-written CSS/SCSS in the frontend.
|
||||
|
||||
## 4. Docker + Gitea container registry
|
||||
|
||||
Per Gitea's own docs:
|
||||
- Login: `docker login gitea.example.com` (use a personal access token instead of a password when 2FA/OAuth is enabled).
|
||||
- Gitea's documented image naming format is **`{registry}/{owner}/{image}`**, e.g. `gitea.example.com/testuser/myimage`, and sub-paths are allowed (`gitea.example.com/testuser/my/image`). Tags are case-insensitive.
|
||||
- Push: `docker push gitea.example.com/{owner}/{image}:{tag}`.
|
||||
- The registry is OCI-compliant and works with any OCI client (Docker, Podman, Buildah, Skopeo).
|
||||
— [Container Registry, docs.gitea.com](https://docs.gitea.com/usage/packages/container/)
|
||||
|
||||
**Applied to this repo**: remote is `https://git.arthurerlich.de/haylan/Smart-Tablet-Screen`, so the prod image should be tagged and pushed as `git.arthurerlich.de/haylan/smart-tablet-screen:latest`.
|
||||
|
||||
**Repo/infra discovery already done** (carried over, not re-verified here):
|
||||
- No `.gitea` directory, no CI workflow files, and no `~/.docker/config.json` exist in this repo/machine yet — CI (Gitea Actions or manual push) and Docker registry auth both need to be set up from scratch.
|
||||
- No `tea` CLI config found locally.
|
||||
- The user's Gitea instance (`git.arthurerlich.de`) runs as Docker Swarm services (`git_gitea-server`, `git_gitea-db`, `git_backup`) on a host called "proxmox", per `~/Documents/code/restore-gitea.sh`, with backups on a "datasservices" storage host. This is operational context for where the registry lives, not registry credentials — no push credentials exist anywhere in this discovery and none are fabricated here.
|
||||
- The `gitea-tea` skill drives the `tea` CLI for issues/PRs/wiki but explicitly does **not** cover container registry pushes — registry auth/push must be done via plain `docker login`/`docker push` as documented above, or via a Gitea Actions CI workflow (not yet present in this repo).
|
||||
|
||||
## 5. Playwright (E2E)
|
||||
|
||||
Per Playwright's own docs:
|
||||
- "Playwright Test is an end-to-end test framework for modern web apps. It bundles test runner, assertions, isolation, parallelization and rich tooling."
|
||||
- Supports Node.js, Python, Java, .NET bindings; drives Chromium, WebKit, and Firefox, headless or headed, with native mobile emulation for Chrome (Android) and Mobile Safari.
|
||||
- Playwright's own site documents a dedicated **MCP** section (`/mcp/introduction`) and an **Agent CLI** section, confirming Playwright supports being driven as an MCP server / programmatically by an agent, in addition to its standard test-runner usage.
|
||||
— [Playwright docs — Installation/Intro, playwright.dev](https://playwright.dev/docs/intro)
|
||||
|
||||
**Fit check**: since this project targets a fixed Chrome/Android-kiosk viewport, Playwright's Chromium engine with device/viewport emulation is a good match for E2E coverage of the dashboard.
|
||||
|
||||
**Locally available tooling**:
|
||||
- A `playwright-e2e-testing` skill exists locally (noted per instructions, not invoked).
|
||||
- No separate Playwright MCP server was found configured in this environment.
|
||||
- The `claude-in-chrome` capability is the discovered tool for live, real-browser interaction (click, screenshot, console logs) against a real Chrome instance in this environment — use it for ad hoc manual verification of UI changes; Playwright remains the tool for the actual automated, repeatable E2E test suite.
|
||||
|
||||
## 6. PHPUnit + Symfony testing
|
||||
|
||||
- Symfony's own testing docs describe three test tiers on top of PHPUnit: **unit tests** (plain PHPUnit, no framework), **integration tests** (extend `KernelTestCase`, boot the DI container), and **application/functional tests** (extend `WebTestCase`, use `$client->request()` plus `assertResponseIsSuccessful()`/`assertSelectorTextContains()` style assertions). Setup is via `composer require --dev symfony/test-pack` and `php bin/phpunit`; test env config lives in `.env.test` / `.env.test.local` (`.env.local` is deliberately not loaded in the test environment). — [Testing, symfony.com/doc](https://symfony.com/doc/current/testing.html)
|
||||
- PHPUnit itself is described by its own site as "the testing framework for PHP"; current stable is PHPUnit 13 (as of Feb 2026), maintained by Sebastian Bergmann. — [phpunit.de](https://phpunit.de/)
|
||||
|
||||
**Locally available tooling**: a `phpunit-best-practices` skill exists locally (noted per instructions, not invoked).
|
||||
|
||||
**Conclusion**: use Symfony's standard three-tier setup — unit tests for pure PHP logic (e.g. calendar-event normalization/merging), `KernelTestCase` integration tests for the CalDAV/Graph client services, `WebTestCase` functional tests for the calendar API controller endpoints.
|
||||
|
||||
## 7. Frontend framework: Astro vs plain TypeScript/Vite
|
||||
|
||||
Per Astro's own docs:
|
||||
- Astro has two output modes as of Astro 5 (the old `hybrid` mode was folded into `static`): **`static`** (default — every page prerendered to flat HTML at build time) and **`server`** (on-demand rendering per request, requires an adapter). — [On-demand Rendering, docs.astro.build](https://docs.astro.build/en/guides/on-demand-rendering/)
|
||||
- Astro's own guidance: *"Start with the default 'static' mode until you are sure that most or all of your pages will be rendered on demand!"* — i.e. Astro is optimized around mostly-static, multi-page content sites, with `server` mode as an opt-in escape hatch, not the primary design target.
|
||||
- Astro's islands architecture renders components to static HTML at build time and hydrates only interactive islands with JS — a good fit for content pages with sparse interactivity, not for an app that is a single always-on, continuously-updating client view.
|
||||
|
||||
**Assessment for this project**: the dashboard is a **single page**, always mounted, continuously polling a Symfony JSON API and re-rendering a calendar view client-side — there is no multi-page routing, no meaningfully "static, prerenderable" content, and no SEO/first-load-performance concern (it's a fixed kiosk browser, not a public site). Astro's core value propositions (partial hydration across many mostly-static pages, build-time prerendering, content collections) don't apply here; using Astro would mean running one perpetual "server island" and getting none of Astro's benefits while carrying its page-oriented conventions (file-based routing, `.astro` components) for what is really one long-lived app shell.
|
||||
|
||||
**Recommendation**: plain **TypeScript + Vite** (no meta-framework). Symfony already owns the backend/API surface; the frontend only needs a bundler, dev server, and TS type-checking for a single-page app that fetches from the Symfony API and renders a calendar. This avoids Astro's per-page rendering model entirely and keeps the frontend a thin, single SPA build.
|
||||
|
||||
## 8. Calendar UI library
|
||||
|
||||
Evaluated against each library's own docs:
|
||||
|
||||
- **FullCalendar** — supports vanilla JS/TypeScript "core" usage plus first-party React/Vue/Angular/Preact/Web Component wrappers; ships full TypeScript type support. Core (month/week/day/list/timeGrid views, drag/resize, i18n, timezone support) is open source (MIT); some views (Timeline, vertical Resource view, print) are gated behind a paid **Premium** tier. — [FullCalendar Docs, fullcalendar.io/docs](https://fullcalendar.io/docs)
|
||||
- **Schedule-X** — an open-source TypeScript event calendar, explicitly positioned by its own docs as "a modern alternative to FullCalendar and react-big-calendar," usable directly in vanilla TS or with framework component wrappers (React/Angular/Vue/Svelte/Preact); ships day/week/month views, recurring events, dark mode, i18n, accessibility-focused patterns. — [schedule-x.dev](https://schedule-x.dev/), [schedule-x/schedule-x GitHub](https://github.com/schedule-x/schedule-x)
|
||||
|
||||
**Recommendation**: **FullCalendar** for this project — the free/MIT core (month + week/day timeGrid + list views) covers everything this fixed-layout kiosk dashboard needs (no drag/resize/resource-planning premium features required), it has first-party vanilla-TS support matching the plain-TS/Vite frontend decision above, and it's the most mature/battle-tested option for a monitor-style read-mostly display. Schedule-X is a credible lighter-weight fallback if FullCalendar's bundle size or styling model proves awkward to theme for the kiosk's fixed layout.
|
||||
|
||||
**Component libraries**: no general UI component library was researched in depth beyond the calendar widget itself, since the fixed-layout, single-screen kiosk dashboard has a small, custom surface (a calendar grid plus maybe a status/clock strip) that doesn't obviously benefit from a general component-library dependency — this is a YAGNI call, revisit only if the UI grows multiple distinct widget types needing shared interactive components (modals, tabs, etc.).
|
||||
|
||||
## 9. Reverse proxy / deployment
|
||||
|
||||
No first-party doc research was needed beyond what's already fixed by the task: the prod Docker image (tagged `:latest`, pushed to `git.arthurerlich.de/haylan/smart-tablet-screen:latest` per §4) runs on the user's local/Proxmox-hosted Docker environment behind an existing reverse proxy, exposed at `smart-screen.home`. This is an infra/ops detail for the eventual `devops-engineer`/`docker-expert` skill-driven work, not a research question with a "primary source" to cite — noted here only for completeness of the stack list.
|
||||
|
||||
---
|
||||
|
||||
## Locally available Skills/MCP servers relevant to each decision
|
||||
|
||||
| Area | Skill/MCP found locally | Coverage |
|
||||
|---|---|---|
|
||||
| Symfony + calendar integration | none specific | n/a — plain Symfony/PHP work |
|
||||
| Docker image builds | `docker-expert` | Dockerfile authoring, multi-stage builds, security hardening |
|
||||
| CI/CD, Gitea Actions | `devops-engineer` | Pipelines, Dockerfiles, deployment automation — general, not Gitea-registry-specific |
|
||||
| Gitea issues/PRs/wiki | `gitea-tea` | Drives `tea` CLI — explicitly does **not** cover container registry pushes (see §4) |
|
||||
| BEM/CSS | none specific | n/a |
|
||||
| Playwright E2E | `playwright-e2e-testing` | Present locally; noted, not invoked per task instructions |
|
||||
| PHPUnit | `phpunit-best-practices` | Present locally; noted, not invoked per task instructions |
|
||||
| Astro / frontend framework choice | none specific | n/a |
|
||||
| Calendar UI library | none specific | n/a |
|
||||
| Live browser testing | `claude-in-chrome` | The available "drive a real Chrome browser" tool in this environment (click, screenshot, console logs) — no separate Playwright MCP server is configured here |
|
||||
|
||||
---
|
||||
|
||||
## Recommended stack (summary)
|
||||
|
||||
- **Backend**: Symfony (as specified), organized with standard MVC — controllers thin, calendar-aggregation/normalization logic in dedicated services, kept unit-testable in isolation from HTTP/DB.
|
||||
- **Calendar sync — Mail-in-a-Box**: CalDAV client against each account's Nextcloud CalDAV URL (RFC 4791 / Nextcloud's documented `/remote.php/dav/calendars/<user>/` path) — it's the standards-based protocol Mail-in-a-Box actually exposes; skip Z-Push/ActiveSync, it's a mobile-sync path, not a scriptable API.
|
||||
- **Calendar sync — Outlook/Exchange**: Microsoft Graph API via OAuth 2.0 authorization-code flow (Entra app registration, `tenant=common`, `offline_access` for refresh tokens) — Graph is Microsoft's only supported, future-proof calendar API for personal Microsoft accounts; CalDAV isn't offered and EWS is being retired.
|
||||
- **Frontend framework**: plain **TypeScript + Vite**, no meta-framework — Astro's static/islands model targets multi-page content sites, not a single always-on kiosk SPA; Symfony already owns the backend, so the frontend only needs a lean TS build.
|
||||
- **Calendar UI**: **FullCalendar** (core/MIT) — mature, first-party vanilla-TS support, month/week/list views cover the read-mostly kiosk display without needing its paid Premium tier.
|
||||
- **CSS**: **BEM** (as specified) — block/element/modifier naming (`__`, `_`) applied directly per bem.info's quick-start convention.
|
||||
- **E2E testing**: **Playwright** — first-party Chromium engine with device/viewport emulation fits the fixed Android/Chrome kiosk target; use the local `playwright-e2e-testing` skill when writing the suite; use `claude-in-chrome` for ad hoc live-browser checks during development, not as a substitute for the automated suite.
|
||||
- **Unit/integration testing**: **PHPUnit** via Symfony's own three-tier convention (unit / `KernelTestCase` / `WebTestCase`) — use the local `phpunit-best-practices` skill when writing tests.
|
||||
- **Docker/CI**: two Dockerfiles (dev, prod), prod built and pushed as `git.arthurerlich.de/haylan/smart-tablet-screen:latest` via `docker login`/`docker push` per Gitea's documented OCI registry path format; no Gitea Actions workflow exists yet in this repo — CI automation is a follow-up, not yet configured, and would use `devops-engineer`/`docker-expert` skills.
|
||||
- **Reverse proxy/deployment**: existing local reverse-proxy setup exposing the prod container as `smart-screen.home` — no new research needed here, this is ops execution against the infra already discovered (Proxmox-hosted Docker/Swarm).
|
||||
Reference in New Issue
Block a user