Add Datenschutzerklärung, two blog posts, and a /uses colophon

- datenschutz.astro: DSGVO privacy policy (server logfiles, Hetzner as
  Auftragsverarbeiter per Art. 28, cookieless Umami, self-hosted fonts,
  data-subject rights, supervisory-authority complaint).
- consts.ts: extract shared LEGAL entity data; Impressum now reads from it.
- blog: "Why my calendar app has no internet permission" and
  "Open standards as a constraint, not a checkbox".
- uses.astro: colophon of the site stack, Floret apps, and self-hosted infra.
- Footer: add Uses + Datenschutz links alongside Impressum.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 14:54:49 +02:00
parent 108e790621
commit d1acda5a93
7 changed files with 353 additions and 16 deletions

View File

@@ -6,7 +6,10 @@ const year = new Date().getFullYear();
<footer class="site-footer">
<div class="site-footer__inner">
<p>© {year} {SITE.author} · <a href="/impressum">Impressum</a></p>
<p>
© {year} {SITE.author} · <a href="/uses">Uses</a> ·
<a href="/impressum">Impressum</a> · <a href="/datenschutz">Datenschutz</a>
</p>
{
SOCIALS.length > 0 && (
<nav aria-label="Social" class="socials">

View File

@@ -8,6 +8,18 @@ export const SITE = {
locale: 'en_US',
} as const;
// Legal entity details — single source of truth for the Impressum and
// Datenschutzerklärung. Registered Einzelunternehmen (Kleinunternehmer per
// § 19 UStG; no VAT ID, not in the Handelsregister). `phone` renders only if set.
export const LEGAL = {
business: 'IT-Dienstleister | Jean-Luc Makiola', // full Gewerbe name (§ 5 DDG)
person: SITE.author, // natural person responsible — § 18 Abs. 2 MStV
street: 'Mahlerstraße 10',
city: '14772 Brandenburg an der Havel',
email: 'mail@jeanlucmakiola.de',
phone: '',
} as const;
// Social / external links shown in the footer. `icon` is an Iconify name.
export const SOCIALS: { label: string; href: string; icon: string }[] = [
{ label: 'Gitea', href: 'https://gitea.jeanlucmakiola.de/makiolaj', icon: 'simple-icons:gitea' },

View File

@@ -0,0 +1,67 @@
---
title: Why my calendar app has no internet permission
description: >-
Calendula can't talk to the network — and that's the whole design. A look at
building on Android's CalendarContract instead of reinventing a sync stack.
pubDate: 2026-06-28
tags: [android, architecture, calendula]
draft: false
---
Open Calendula's manifest and you'll notice something missing: there is no
`android.permission.INTERNET`. The app physically *cannot* reach the network.
For a calendar — a category of app practically synonymous with cloud accounts —
that sounds like a missing feature. It's the opposite. It's the design.
## The usual shape of a calendar app
Most calendar apps own their data. They sign you into an account, pull events
down over the provider's API, cache them in a private database, and reconcile
changes with their own sync engine. That sync stack is the hard part: conflict
resolution, recurring-event expansion, time zones, retries, token refresh. It's
also the part that locks you in — your events live in *their* schema, reachable
only through *their* app.
## The other option Android already gives you
Android ships a system calendar database, exposed through
[`CalendarContract`](https://developer.android.com/reference/android/provider/CalendarContract).
Anything synced to your device lands there: a CalDAV account via
[DAVx5](https://www.davx5.com/), your Google calendar, a local on-device
calendar, a read-only WebCal subscription. They all show up through the same
content provider, with the same columns.
Calendula is a pure front-end over that provider. It reads events through
`CalendarContract`, and when you create or edit something, it writes straight
back. Whatever sync adapter put the calendar on your device picks the change up
and pushes it out. There is **no own database and no reinvented sync stack**
so there is nothing for the app to phone home about.
## What you get for free
Dropping the network permission isn't a sacrifice; it's what falls out of the
architecture:
- **Your data stays yours, and stays portable.** Events live in the platform's
store and in your CalDAV account — not in a schema only Calendula understands.
- **Privacy is structural, not a promise.** Zero telemetry and zero analytics
are easy to claim. *No internet permission* is enforced by the OS: even if I
wanted to exfiltrate your schedule, the app couldn't.
- **Reminders still work** — Calendula delivers them itself as notifications,
because Android delegates reminder delivery to the installed calendar app.
- **Any account "just appears."** Add a new CalDAV account in DAVx5 and it
surfaces in Calendula with no integration work, because the integration point
is the OS, not a vendor API.
## The trade-off, stated honestly
A front-end can only be as good as the provider beneath it. Calendula doesn't
add its own server-side features, and it relies on a sync adapter like DAVx5
being installed to actually move bytes. That's a deliberate line: I'd rather put
a thoughtful Material 3 Expressive interface on an open protocol than own a sync
stack I'd inevitably get subtly wrong.
The same idea drives the rest of the [Floret family](/work) — Agendula is the
exact same bet, made on the OpenTasks provider instead of the calendar one.
Different content, identical philosophy: build the part that's worth building,
and let open standards carry the rest.

View File

@@ -0,0 +1,56 @@
---
title: Open standards as a constraint, not a checkbox
description: >-
CalDAV, iCalendar, OpenTasks, DecSync — why I treat open standards as a hard
boundary for what the Floret apps are allowed to do.
pubDate: 2026-06-27
tags: [open-standards, caldav, android]
draft: false
---
"Supports open standards" usually means a feature in a list — an export button,
an import dialog, an optional CalDAV setting buried three screens deep. For the
[Floret apps](/work) it's the other way round: open standards are the boundary,
and everything that would step outside them is simply out of scope.
## The lane
The standards are deliberately boring and well-proven:
- **[CalDAV](https://datatracker.ietf.org/doc/html/rfc4791)** and
**[iCalendar](https://datatracker.ietf.org/doc/html/rfc5545)** for calendar
events — the protocol and the data format the rest of the ecosystem already
speaks.
- **[OpenTasks](https://github.com/dmfs/opentasks)' `TaskContract` provider**
for tasks, which stores CalDAV VTODOs on the device.
- **[DecSync](https://github.com/39aldo39/DecSync)** for peer-to-peer sync
without a server in the middle.
Calendula reads and writes events through Android's `CalendarContract`; Agendula
does the same over the OpenTasks provider. Neither app owns a database. The sync
adapter you already trust — DAVx5, SmoothSync, DecSync — moves the bytes.
## Why make it a hard boundary
Treating the standard as a constraint changes which decisions are even on the
table. A proprietary task service with a slick API is permanently off the
roadmap — not because it's bad, but because integrating it would mean owning a
sync stack and tying your data to one vendor's schema. The moment an app starts
reconciling its own copy of your data against someone's cloud, the simplicity
that made it trustworthy is gone.
The constraint also keeps the apps **reproducible**. There are no secret API
keys to embed, no SDKs that pull in closed dependencies, nothing that would stop
an app from building clean for [F-Droid](https://f-droid.org/). What goes in is
exactly what you can read in the source.
## The cost, and why it's worth paying
Living inside the standard means some things are genuinely harder. Provider APIs
have rough edges; recurrence rules and time zones in iCalendar are a deep well;
and you inherit whatever the underlying sync adapter does or doesn't support.
You don't get to paper over those gaps with a server you control.
That's the right trade. An app built on an open standard is one you can leave
without losing anything — your events and tasks were never hostage to it in the
first place. The interface is mine to get right; the data was always yours.

138
src/pages/datenschutz.astro Normal file
View File

@@ -0,0 +1,138 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { SITE, LEGAL } from '../consts';
// Contact data is shared with the Impressum via the LEGAL object in consts.ts.
// Hosting provider (Auftragsverarbeiter): Hetzner Online GmbH — an AVV per
// Art. 28 DSGVO must be concluded (Hetzner provides one in the customer panel).
---
<BaseLayout
title="Datenschutzerklärung"
description={`Datenschutzerklärung nach DSGVO für ${SITE.url}.`}
width="narrow"
>
<article class="prose">
<h1 class="page-title">Datenschutzerklärung</h1>
<h2>1. Verantwortlicher</h2>
<p>
Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist:
</p>
<p>
{LEGAL.business}<br />
{LEGAL.street}<br />
{LEGAL.city}<br />
E-Mail: <a href={`mailto:${LEGAL.email}`}>{LEGAL.email}</a>
</p>
<h2>2. Grundsätzliches</h2>
<p>
Der Schutz Ihrer personenbezogenen Daten ist mir wichtig. Diese Website ist
bewusst datensparsam aufgebaut: Es werden keine Cookies gesetzt, keine
Inhalte von Drittanbietern (z.&nbsp;B. Google Fonts, CDNs oder Werbenetze)
nachgeladen und keine geräteübergreifenden Profile gebildet. Schriftarten
werden vom eigenen Server ausgeliefert, sodass beim Seitenaufruf keine
Verbindung zu Dritten entsteht.
</p>
<h2>3. Hosting und Server-Logfiles</h2>
<p>
Diese Website wird auf selbst verwalteter Infrastruktur betrieben, die bei
der <strong>Hetzner Online GmbH</strong>, Industriestr.&nbsp;25,
91710&nbsp;Gunzenhausen, Deutschland, angemietet ist. Hetzner verarbeitet
die nachfolgend genannten Daten als Auftragsverarbeiter auf Grundlage eines
Vertrags zur Auftragsverarbeitung gemäß Art.&nbsp;28 DSGVO. Die Server
stehen in Rechenzentren innerhalb der Europäischen Union (Deutschland bzw.
Finnland); eine Datenübermittlung in ein Drittland findet nicht statt.
</p>
<p>
Beim Aufruf der Seiten erhebt und speichert der Server automatisch
Informationen in sogenannten Server-Logfiles, die Ihr Browser übermittelt:
</p>
<ul>
<li>IP-Adresse des anfragenden Geräts,</li>
<li>Datum und Uhrzeit des Zugriffs,</li>
<li>Name und URL der abgerufenen Datei,</li>
<li>übertragene Datenmenge sowie Meldung über erfolgreichen Abruf,</li>
<li>verwendeter Browsertyp und dessen Version,</li>
<li>Betriebssystem sowie die zuvor besuchte Seite (Referrer), sofern übermittelt.</li>
</ul>
<p>
Diese Verarbeitung erfolgt zum Zweck der technischen Bereitstellung,
Stabilität und Sicherheit der Website. Rechtsgrundlage ist das berechtigte
Interesse an einem fehlerfreien und sicheren Betrieb gemäß
Art.&nbsp;6&nbsp;Abs.&nbsp;1&nbsp;lit.&nbsp;f DSGVO. Die Logfiles werden
nicht mit anderen Datenquellen zusammengeführt und nach kurzer Zeit
gelöscht, soweit sie nicht zur Aufklärung eines konkreten
Missbrauchsfalls benötigt werden.
</p>
<h2>4. Webanalyse mit Umami</h2>
<p>
Zur Reichweitenmessung nutze ich <strong>Umami</strong>, eine
datenschutzfreundliche Analyse-Software, die ich selbst hoste. Umami
arbeitet <strong>ohne Cookies</strong> und ohne über mehrere Websites
hinweg wiedererkennbare Kennungen. Erhoben werden ausschließlich
aggregierte, anonymisierte Nutzungsdaten etwa aufgerufene Seiten,
ungefähre Herkunft (Land), Gerätetyp und Verweisquelle. Es werden keine
personenbezogenen Profile gebildet und keine Daten an Dritte weitergegeben.
</p>
<p>
Rechtsgrundlage ist das berechtigte Interesse an einer datensparsamen
Auswertung der Websitenutzung gemäß
Art.&nbsp;6&nbsp;Abs.&nbsp;1&nbsp;lit.&nbsp;f DSGVO. Da keine Cookies oder
vergleichbaren Technologien zum Einsatz kommen und keine
personenbeziehbaren Daten gespeichert werden, ist hierfür keine
Einwilligung erforderlich.
</p>
<h2>5. Kontaktaufnahme</h2>
<p>
Wenn Sie mich per E-Mail kontaktieren, werden die von Ihnen mitgeteilten
Daten (Ihre E-Mail-Adresse sowie der Inhalt Ihrer Nachricht) zur
Bearbeitung Ihrer Anfrage verarbeitet und gespeichert. Rechtsgrundlage ist
Art.&nbsp;6&nbsp;Abs.&nbsp;1&nbsp;lit.&nbsp;f DSGVO (Beantwortung Ihrer
Anfrage) bzw. Art.&nbsp;6&nbsp;Abs.&nbsp;1&nbsp;lit.&nbsp;b DSGVO, sofern
die Anfrage auf den Abschluss eines Vertrags gerichtet ist. Die Daten
werden gelöscht, sobald sie für die Bearbeitung nicht mehr erforderlich
sind und keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
</p>
<h2>6. SSL-/TLS-Verschlüsselung</h2>
<p>
Diese Seite nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung. Eine
verschlüsselte Verbindung erkennen Sie am Schloss-Symbol in der
Adresszeile Ihres Browsers und am Präfix „https://“.
</p>
<h2>7. Ihre Rechte</h2>
<p>Ihnen stehen nach der DSGVO gegenüber dem Verantwortlichen folgende Rechte zu:</p>
<ul>
<li>Auskunft über die zu Ihrer Person gespeicherten Daten (Art.&nbsp;15 DSGVO),</li>
<li>Berichtigung unrichtiger Daten (Art.&nbsp;16 DSGVO),</li>
<li>Löschung Ihrer Daten (Art.&nbsp;17 DSGVO),</li>
<li>Einschränkung der Verarbeitung (Art.&nbsp;18 DSGVO),</li>
<li>Datenübertragbarkeit (Art.&nbsp;20 DSGVO),</li>
<li>
Widerspruch gegen die Verarbeitung, soweit diese auf
Art.&nbsp;6&nbsp;Abs.&nbsp;1&nbsp;lit.&nbsp;f DSGVO beruht
(Art.&nbsp;21 DSGVO).
</li>
</ul>
<p>
Zur Ausübung Ihrer Rechte genügt eine formlose Nachricht an die oben
genannte E-Mail-Adresse.
</p>
<h2>8. Beschwerderecht bei der Aufsichtsbehörde</h2>
<p>
Unbeschadet eines anderweitigen verwaltungsrechtlichen oder gerichtlichen
Rechtsbehelfs steht Ihnen das Recht auf Beschwerde bei einer
Datenschutz-Aufsichtsbehörde zu, insbesondere in dem Mitgliedstaat Ihres
Aufenthaltsorts, Ihres Arbeitsplatzes oder des Orts des mutmaßlichen
Verstoßes, wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer Daten
gegen die DSGVO verstößt (Art.&nbsp;77 DSGVO).
</p>
</article>
</BaseLayout>

View File

@@ -1,20 +1,6 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { SITE } from '../consts';
// ─── Legal data ──────────────────────────────────────────────────────────────
// REPLACE the «…» placeholders with real, legally accurate values before going
// live. A German Impressum (§ 5 DDG) requires a summonable postal address —
// a P.O. box / Postfach is NOT sufficient. Leave `phone` empty to omit it.
const IMPRESSUM = {
business: 'IT-Dienstleister | Jean-Luc Makiola', // full Gewerbe name (§ 5 DDG)
person: SITE.author, // natural person responsible — 'Jean-Luc Makiola' (§ 18 MStV)
street: 'Mahlerstraße 10',
city: '14772 Brandenburg an der Havel',
email: 'mail@jeanlucmakiola.de',
phone: '', // e.g. '+49 ...' — rendered only if set
};
// ─────────────────────────────────────────────────────────────────────────────
import { SITE, LEGAL as IMPRESSUM } from '../consts';
---
<BaseLayout

75
src/pages/uses.astro Normal file
View File

@@ -0,0 +1,75 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
// A /uses colophon — the stack behind this site and the Floret apps.
// Add a "Desk / hardware" section here if you'd like to list your machine,
// editor, and peripherals — left out for now to avoid guessing.
---
<BaseLayout
title="Uses"
description="The stack behind this site, the Floret apps, and the infrastructure they run on."
width="narrow"
>
<article class="prose">
<h1 class="page-title">Uses</h1>
<p>
A running colophon of the tools and stack behind this site, the
<a href="/work">Floret apps</a>, and the infrastructure they run on. The
throughline: open, self-hostable standards, and as little reliance on
third-party services as I can manage.
</p>
<h2>This website</h2>
<ul>
<li>
Built with <a href="https://astro.build" rel="noopener">Astro</a> — a
static site, no client-side framework, content authored as Markdown in
the repo.
</li>
<li>
Typefaces are <a href="https://rsms.me/inter/" rel="noopener">Inter</a>
and <a href="https://www.jetbrains.com/lp/mono/" rel="noopener">JetBrains
Mono</a>, self-hosted via Fontsource — no Google Fonts CDN, so no
third-party request on page load.
</li>
<li>
Analytics is self-hosted <a href="https://umami.is" rel="noopener">Umami</a>:
cookieless, no cross-site tracking, no personal profiles. See the
<a href="/datenschutz">Datenschutzerklärung</a> for what that means.
</li>
<li>Icons from <a href="https://iconify.design" rel="noopener">Iconify</a> (Material Design Icons + Simple Icons).</li>
</ul>
<h2>The Floret apps</h2>
<ul>
<li><strong>Kotlin</strong> and <strong>Jetpack Compose</strong>, designed in <strong>Material 3 Expressive</strong>.</li>
<li>
No reinvented sync stack — each app is a front-end over a platform
provider (<code>CalendarContract</code>, the OpenTasks
<code>TaskContract</code>) and open standards like CalDAV, iCalendar, and
DecSync.
</li>
<li>
A shared design system, <a href="/work/floret-kit">floret-kit</a>, wired
in as a git submodule via a Gradle composite build — no published
artifacts, so every app stays reproducible.
</li>
<li>Released on <a href="https://f-droid.org" rel="noopener">F-Droid</a>, MIT-licensed, zero telemetry.</li>
</ul>
<h2>Self-hosted infrastructure</h2>
<p>Code, translations, builds, and this site all run on infrastructure I host myself.</p>
<ul>
<li><a href="https://gitea.jeanlucmakiola.de/makiolaj" rel="noopener">Gitea</a> for source hosting and as the home for smaller experiments.</li>
<li><a href="https://weblate.org" rel="noopener">Weblate</a> for community translations of the apps.</li>
<li><a href="https://coolify.io" rel="noopener">Coolify</a> to build and deploy this site.</li>
<li>Umami for the privacy-respecting analytics above.</li>
</ul>
<p class="muted">
This list grows as the stack does. Spotted something you'd ask about?
<a href="mailto:mail@jeanlucmakiola.de">Get in touch</a>.
</p>
</article>
</BaseLayout>