A Next.js 16 App Router site for Avery Freeman's Markdown/MDX articles and pinned projects. The interface uses Tailwind CSS 4, daisyUI 5, Catppuccin Mocha/Frappe themes, and locally packaged Fontsource families.
pnpm install
pnpm devOpen http://localhost:3000. Before handoff, run:
pnpm lint
pnpm typecheck
pnpm build
pnpm test:e2e- Articles are read from the private, read-only git submodule at
content/articles/. - The public Python/Airflow project is available separately at
content/pipeline/. - Project profiles are read from
projects/; drop a.mdor.mdxfile there with the same frontmatter shape as the existing five profiles. - Article
Tagsfrontmatter is aggregated at build/dev time for the Articles tag cloud and filter. - Relative article attachments are served through the constrained
/article-assets/route.
Do not edit files inside either submodule from this parent project. The
integration README and diagram under content/ are parent-owned documentation.
The .gitmodules entries point at the hosted pipeline and private article
repositories. A checkout needs GitHub access to the private article repository.
After authenticating, initialize both submodules with:
git submodule update --init --recursiveThis repository is the public Next.js presentation layer for UnixGreybeard.Org. It owns the web application, project profiles, visual design system, and the integration points that read private article content. It does not execute the Python pipeline and it does not contain the published article tree itself.
UnixGreybeard.Org (public site repository)
├── app/ Next.js App Router routes and asset handler
├── components/ Interactive UI, carousels, theme toggle, layout
├── lib/ Article and project discovery/read models
├── projects/ Public project profile Markdown/MDX
├── content/articles/ Private submodule: published Markdown + raw LFS data
└── content/pipeline/ Public submodule: Python workers, Airflow DAG, tests
└── takeout_downloader_script/
Optional nested acquisition submodule
The public repositories are UnixGreybeard.Org and Airflow-AI_Search-to-Blog-Article-Pipeline. The article and interim raw-data repository is the private UnixGreybeard.Org-Articles.
app/defines the home page, article index and article routes, project index and project routes, and the constrained article-asset route.lib/articles.tsreads the private article submodule fromcontent/articles/, parses frontmatter, builds category/tag data, and exposes the article read model to the server-rendered pages.lib/projects.tsreads the five public project profiles underprojects/.components/contains the reusable header, footer, hero, sidebar, article carousel, project-details carousel, word animation, and theme switcher.app/globals.css, Tailwind CSS 4, daisyUI 5, and the local Fontsource packages provide the shared responsive visual language and theme tokens.next.config.tsandmdx-components.tsxconfigure local Markdown/MDX rendering. The application is a read-only consumer of content at build and request time; the browser never talks directly to the Python pipeline.
The Python submodule owns ingestion and editorial transformation. Its
google_takeout_articles Airflow DAG runs daily at 03:00 America/Los_Angeles
or by manual trigger. The procedure is:
- Discover
.zip,.tar,.tar.gz, and.tgzfiles in the configuredTAKEOUT_INBOX. - Validate archive members and SHA-256 values, then extract safely into a run-specific staging directory.
- Normalize Google activity records into deterministic Markdown with frontmatter, category assignment, provenance, content hashes, and linked attachments.
- Reject already completed or duplicate records and apply the bounded model batch limit.
- Use local
llama.cppfor conversation cleanup, topic splitting, and a document-level brief. - Score public interest, technical value, article potential, and salvageability through the configured OpenRouter provider, then branch to rewrite, editorial review, or rejection.
- Rewrite only the selected articles with the local model and bounded external research. Research records source URLs and extraction limits.
- Validate required frontmatter, taxonomy, body length, and absence of transcript markers before atomically publishing into the matching category directory in the private article repository.
- Record dispositions, completed-record hashes, score records, rewrite originals, and run manifests below the run-specific pipeline state.
taxonomy.py is the only category source of truth. validate_content.py, the
taxonomy migration tool, and the pipeline tests enforce the publication shape.
The Airflow Compose stack uses Postgres for Airflow metadata and pools model,
OpenRouter, and serialized content-mutation work. Secrets are mounted as files
and are not passed through task arguments or logs.
content/articles/ is a private submodule because it contains the published
article tree and source-derived material. It currently also contains one
canonical raw Google Takeout archive under raw-takeout/, tracked with Git
LFS, as an interim safety measure. The duplicate local copy was not stored a
second time. Generated extraction, staging, and rewrite workspaces remain
outside the publication tree.
The intended backup design is to move durable raw archives, extracted media,
rewrite originals, completed-record state, migration manifests, and checksums
to an encrypted external backup location with dated snapshots and retention.
The private Git LFS copy is a temporary holding area, not the final backup
strategy. The optional takeout_downloader_script can acquire archives, but it
is not imported or invoked automatically by the Airflow DAG.
The diagram is a GitHub-compatible raster rendering of the interactive D3 relationship graph. Dashed edges indicate the proposed backup flow.
Saved viewport captures of omp.sh and the public GitHub profile live in docs/references/. Carousel UX tradeoffs are documented in docs/CAROUSEL_UX.md; word-cloud alternatives are documented in WORD_CLOUD_IDEAS.md.
