# 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//`, 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//` 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).