A calm, typography-first publishing platform for the web and inbox.
Markdown is canonical. Everything else — HTML, RSS, JSON Feed, search indexes, newsletters, and provider adapters — is generated or operational state.
Katakata is dogfooded in production by Kamantara, a private editorial publication. Kamantara's editorial content, mailbox data, and deployment state are intentionally kept outside this public repository.
Write once. Own forever. Publish anywhere.
See docs/MASTER_SPECIFICATION.md for
the full vision, philosophy, and architecture. This README covers only
what's needed to run what exists today.
The authenticated owner experience now includes:
- a sparse dashboard with linked Visits, Posts, Drafts, and Inbox cards;
- a filtered content index at
/posts; - analytics at
/analytics; - a global settings control desk at
/dashboard/settings; - one
/mailworkspace for reader correspondence readiness and newsletter campaign work.
The inbox boundary is provider-neutral. IMAP is the first planned adapter, but request-time IMAP access is prohibited. Until a scheduled sync adapter is configured, Inbox shows a non-secret setup state while campaign work remains available.
- PHP 8.5 or later with OpenSSL and PDO SQLite
- Composer (optional — only needed to run the test suite via PHPUnit)
cp .env.example .env
php bin/katakata serve
# or: php -S 127.0.0.1:8000 -t publicVisit http://127.0.0.1:8000/ for the homepage,
http://127.0.0.1:8000/healthz for the health check, or a post's
canonical /{year}/{month}/{slug} URL. The complete published archive is
available at /archive; the authenticated owner dashboard is at /dashboard;
content management is at /posts; analytics is at /analytics; global
settings are at /dashboard/settings; Mail is at /mail; feeds are available
at /feed.xml and /feed.json; and author archives are at /authors/{slug}.
For a complete installation walkthrough, see
docs/operations/self-hosting.md.
config/nginx/katakata.conf serves katakata.local over HTTPS and redirects
HTTP to HTTPS. Create the certificate and private key locally at
config/nginx/ssl/katakata.local.crt and
config/nginx/ssl/katakata.local.key; they are deliberately ignored and must
never be committed. When testing an isolated worktree, use a local vhost copy
whose root points at that worktree's public/ directory rather than editing
the tracked configuration.
php bin/katakata about
php bin/katakata routes:list
php bin/katakata serve [host]
php bin/katakata content:list
php bin/katakata content:validate
php bin/katakata import:document <path> [--author=name] [--dry-run]
php bin/katakata import:directory <path> [--recursive] [--author=name] [--dry-run]
php bin/katakata draft:create <slug> <title>
php bin/katakata draft:edit <slug>
php bin/katakata draft:schedule <slug> <ISO-8601>
php bin/katakata draft:publish <slug> [ISO-8601]
php bin/katakata publish:due
php bin/katakata revisions:list <slug>
php bin/katakata auth:owner <email> <password>
php bin/katakata auth:invite <email> [admin|editor]
php bin/katakata distribution:publish <post-slug> [newsletter]
php bin/katakata newsletter:dispatch <post-slug>
php bin/katakata mail:work [limit]
php bin/katakata resend:webhooks:check
php bin/katakata threads:sync
php bin/katakata analytics:check
php bin/katakata analytics:prune
php bin/katakata seo:checkcomposer install
composer test
# or: phpunitSet ANALYTICS_SECRET (or APP_KEY) in .env, then run
php bin/katakata analytics:check during deployment. Visit recording is
failure-isolated and never stores raw IP addresses.
The application itself never requires Composer's autoloader to run.
Document import requires PHP's DOM and ZIP extensions. Importing legacy .doc
files additionally requires LibreOffice (soffice or libreoffice) on PATH.
See docs/subsystems/import.md for reconciliation,
metadata, dry-run, and collision behavior.
The canonical production origin is https://katakata.example; the default
administrative and sender address is admin@katakata.example. Production email
uses a named transport selected by MAIL_TRANSPORT, with Resend as the initial
provider and the filesystem driver retained for development.
See the complete mail transport and Resend setup guide.
Reader correspondence is separate from campaign delivery. It is private
operational data, never canonical content, and remains outside Git, public
roots, analytics, and diagnostic logs. See
docs/subsystems/email-client.md.
app/ Application code
bin/ CLI entrypoint
bootstrap/ Shared bootstrap
config/ Immutable configuration
content/ Canonical Markdown content
docs/ Specifications, ADRs, and subsystem docs
public/ Web document root
resources/ Plain PHP views and assets
routes/ Route definitions
storage/ Reproducible runtime files
tests/ PHPUnit suite and fixtures
The content/ folder ships with example content. Only public/ is
web-accessible.
- ADR 0001 — Plain Markdown Storage
- ADR 0002 — PHP Runtime
- ADR 0003 — Static-first Architecture
- ADR 0004 — Threads Discussion Layer
- ADR 0005 — Minimal Front Matter Parser
- ADR 0006 — Plain PHP Views
- ADR 0007 — Filesystem Editorial Transactions
- ADR 0008 — Invite-only Authentication
- ADR 0009 — SQLite Analytics and SEO
- ADR 0010 — IMAP Inbox Adapter
- ADR 0011 — Application-managed Secrets
- ADR 0012 — Public Platform Extraction and Downstream Publications
- ADR 0013 — SQLite Analytics and SEO (superseded by ADR 0009)
- ADR 0014 — Operational and Analytical Data Layers
- Markdown is canonical.
- Files are authoritative.
- Generated artifacts are disposable.
- Writers own their content.
- Configuration is immutable after boot.
- Controllers remain thin; business logic is framework-independent.
- Every subsystem has a single responsibility.
- Every feature must justify its complexity.