This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The source of tabularis.dev — the marketing site, wiki, blog, changelog and plugin registry for Tabularis, an open-source desktop database client. It is a Next.js App Router project that must build as a static export (output: "export" in next.config.ts). It is deployed to GitHub Pages by .github/workflows/static.yml on every push to main.
Hard constraint: no runtime-only Next.js features (getServerSideProps, API routes, on-demand ISR, middleware, runtime image optimization). Images are unoptimized: true. Any new route must be fully expressible at build time.
Package manager is pinned: pnpm 10.33.0 (see packageManager in package.json). Node 20+.
| Task | Command |
|---|---|
| Install | pnpm install |
| Dev server (http://localhost:3000) | pnpm dev |
Full static build into out/ |
pnpm build |
| Refresh upstream app data | pnpm fetch-app-data |
There is no lint or test command. eslint.config.mjs explicitly ignores **/*; do not try to add lint runs to "verify" a change.
pnpm build runs five steps in order, all of which must succeed:
scripts/generate-search-index.mjs→public/search-index.json(Orama index built from wiki + posts + SEO + plugin registry).scripts/generate-sponsors.mjs→public/sponsors.json(re-emitssrc/lib/sponsors.tsas JSON).next build→ emits the site toout/.scripts/generate-latest-posts.mjs→public/latest-posts.json(top-5 posts for the home widget). Runs afternext buildbecause it writes into the build output.scripts/generate-redirects.mjs→ scanscontent/posts/,content/wiki/,content/seo/,content/roadmap/for files with aredirect_from:frontmatter array and emitsmeta refreshHTML stubs underout/pointing at each file's canonical URL. Used to keep legacy/wrong URLs working after a rename (GitHub Pages can't issue real HTTP 301s, so this is the closest equivalent — Google treats a 0-delay meta refresh as a 301). The script aborts if a stub would overwrite a real exported page, which catches typos inredirect_from.
Three pieces of data are fetched from TabularisDB/tabularis at build time by scripts/fetch-app-data.mjs (defaults: TABULARIS_APP_REPO=TabularisDB/tabularis, TABULARIS_APP_REF=main):
| Upstream | Local file | Consumer |
|---|---|---|
src/version.ts |
src/lib/version.ts (re-emitted as a single APP_VERSION export) |
download links, SEO metadata, JSON-LD |
CHANGELOG.md |
CHANGELOG.md |
/changelog page via src/lib/changelog.ts |
plugins/registry.json |
plugins/registry.json |
/plugins page via src/lib/plugins.ts, and the :::plugin <id>::: markdown extension |
These three files are committed to the repo so local dev works without network access. CI overwrites them before build. When editing them locally, be aware CI will blow your changes away — fix upstream instead.
Rebuilds can be triggered from the app repo via a repository_dispatch event of type app-data-updated.
Almost every page is driven by Markdown in content/, loaded by a matching src/lib/<type>.ts module:
| Content dir | Loader | Routes |
|---|---|---|
content/posts/ |
src/lib/posts.ts |
/blog, /blog/[slug], /blog/page/[page], /blog/tag/[tag] |
content/wiki/ |
src/lib/wiki.tsx |
/wiki, /wiki/[slug] |
content/seo/ |
src/lib/seoPages.ts |
/solutions, /solutions/[slug], /compare, /compare/[slug] (section is chosen via frontmatter section: solutions | compare) |
content/roadmap/ |
src/lib/roadmap.ts |
/roadmap, /roadmap/[slug] |
CHANGELOG.md (fetched) |
src/lib/changelog.ts |
/changelog |
plugins/registry.json (fetched) |
src/lib/plugins.ts |
/plugins |
Loaders all follow the same pattern: read files with fs at module scope, parse frontmatter with gray-matter, render body with the shared marked instance from src/lib/markdown.ts. This only works because next build runs Node — do not import these loaders into a client component.
Two exceptions to the loader pattern:
content/home.mdis parsed inline bygetHomeContent()insidesrc/app/page.tsx(not via asrc/lib/home.tsmodule). It's split by#/###headings into the hero, capabilities grid, and closing CTA — edits tohome.mdstructure need to stay compatible with that parser./videosand/videos/[slug]are not markdown-driven. They render from a hardcodedVIDEO_DEMOSarray insrc/lib/videos.ts; add/edit demos there, and remember the referencedpublic/videos/...asset must also exist.
src/app/sitemap.ts fans out over every loader and the videos array, and must be updated when adding a new content type or code-driven route family.
src/lib/markdown.ts is the only place marked should be imported from — it registers two custom fenced-block extensions that work in any Markdown content:
:::plugin <plugin-id>:::→ renders a plugin card using the registry (plugins/registry.json). Plugin id must matchregistry.json.:::newsletter:::→ inserts<div data-newsletter></div>, hydrated on the client.
Blog posts additionally support a :::contributors::: placeholder expanded in getPostBySlug in src/lib/posts.ts. It fires live GitHub API calls at build time (comparing releases and searching merged PRs between two tags) to render per-release contributor avatars. A post's frontmatter release: tag is required for this to resolve.
Code blocks are syntax-highlighted by marked-highlight + highlight.js. The theme is loaded globally in src/app/layout.tsx (atom-one-dark).
@/* maps to src/* (see tsconfig.json). Use it consistently — mixed @/lib/... and ../../lib/... imports in the same file make grep harder.
- Route groups that take params must export
generateStaticParams— pagination pages, tag pages, wiki slugs, roadmap slugs, and both SEO sections already do this. Forgetting it silently drops the route fromout/. export const dynamic = "force-static"is used insrc/app/sitemap.ts; keep any similar declarative-only routes static.public/search-index.json,public/sponsors.json, andpublic/latest-posts.jsonare generated — do not hand-edit.
- New blog post → add a
.mdfile undercontent/posts/with frontmatter:title,date(ISO, include time for same-day ordering),tags(array),excerpt, optionalrelease(e.g."v0.9.20") to link to a Tabularis release and enable:::contributors:::, optionalog: { title, accent, claim, image }for custom OG cards. Reading time is auto-estimated (200 wpm). - Wiki page → frontmatter needs
title,order,excerpt,category(one of the values inWIKI_CATEGORIESinsrc/lib/wiki.tsx). - SEO page → frontmatter needs
section: solutions \| compareplustitle,order,excerpt,description. The section determines which route (/solutions/...vs/compare/...) the page lives under. - Roadmap initiative → frontmatter needs
title,slug,category,status(in-progress | planned | done),order,lede, and optionallyprogressDone/progressTotal/progressLabeland alinks:array. redirect_from:(optional, supported on posts/wiki/seo/roadmap) → an array of absolute URL paths that should redirect to this page. The build emits a staticmeta refreshstub at each path. Use after renaming a slug or fixing a published-but-wrong URL.
Feature or bug reports about the desktop app itself belong on TabularisDB/tabularis, not this repo. This repo is for the site, its content, and the plugin registry consumer.