15 KiB
Documentation Porting Guide
Purpose and Scope
This guide is the canonical rename and replacement map for porting inherited boilerplate documentation to the current generic boilerplate documentation set. It is for documentation updates only: use it to update prose, examples, tables, diagrams, and verification notes in docs, but do not use it as authority to rename runtime imports, package exports, queue names, database identifiers, or historical decision records.
The current web runtime source lives under apps/web/app and apps/web/public. Root scripts still provide the top-level developer entrypoints, while apps/api, apps/worker, and selected packages/* boundaries document or host target extraction surfaces. Documentation should describe the current state precisely instead of implying that every package boundary is fully runtime-wired.
Source Evidence
Use these source files as the confirmed baseline for this guide:
README.md: current quick start, package manager, runtime commands, database setup, worker Redis note, CI summary, and manual E2E policy.docs/architecture-migration-status.md: authoritative boundary status for root runtime,apps/*,packages/*, runtime-wired surfaces, skeleton-only surfaces, UrlUtils migration notes, and feature-pruning interpretation.docs/feature-inventory.md: current pruned-core, plugin-candidate, and remove-candidate classifications for downstream feature documentation.scripts/verify-porting-docs-baseline.mjs: Porting Docs documentation inventory roots and terms that the baseline scanner already tracks..tc/baselines/porting-docs/summary.md: baseline-health handoff and report location.tc/baselines/porting-docs/baseline-health.md.
Downstream documentation slices should compare their proposed edits against the baseline report and explain whether remaining legacy terms are intentional historical references, protected runtime identifiers, or newly introduced drift. Use docs/porting-verification.md as the final post-port documentation checklist before closing downstream porting work.
Rename/Replace Map
| Legacy or ambiguous source term | Preferred documentation term | Apply in docs when | Notes and examples |
|---|---|---|---|
SaasRock, SaasRock Boilerplate, saasrock |
Current boilerplate wording | Product, project, setup, architecture, and roadmap prose that describes the current repository. | Keep historical references only when naming an archived upstream source or old decision context. |
saasrock.com, www.saasrock.com, SaasRock marketing domains |
Neutral local/example domain wording | Domain examples, mail examples, deployment examples, and screenshots that should represent the current project. | Prefer app.localhost:3000 and admin.localhost:3000 for the default web dev loop; use api.localhost:3001 only when the standalone API runtime is started separately. Use example.com / app.example.com for tenant-safe examples unless a deployment-specific domain is explicitly documented. |
SaasRock LLC, old SaasRock legal branding, old trademark/legal wording |
Current project/legal owner wording or neutral placeholder | Legal, privacy, imprint, footer, and branding documentation. | Do not invent legal claims. If ownership is unknown, use a placeholder and mark the document as needing legal review outside the code task summary, not with inline TODO markers. |
| Product-brand variants in prose | Generic boilerplate wording | Human-readable product or brand references. | Keep actual package names, queue names, runner labels, and other runtime identifiers unchanged. |
| Root package display names inherited from old boilerplate | toolctrl-boilerplate |
Documentation that names the root package.json project. |
Confirm against root package.json before changing package-manager or script examples. |
Generic SaaS boilerplate where it means this repository |
Current boilerplate | Repository-specific docs, quick-start text, architecture handoffs, and migration plans. | Keep generic SaaS only when the sentence describes the product category rather than the repository name. |
| Old monolith wording that implies all runtime code is root-only | apps/web web source plus workspace boundaries |
Architecture docs and migration status docs. | State that apps/web/app owns web source while root scripts keep top-level command compatibility and apps/api, apps/worker, and selected packages/* provide target or runtime-wired boundaries. |
Remix runtime wording where it describes current root app execution |
React Router runtime |
Current runtime commands, build, route, and app-shell descriptions. | Preserve remix-i18next and dependency names when referring to actual package names. |
npm, yarn, unpinned package-manager examples |
pnpm with Corepack and pinned pnpm@10.33.0 |
Setup, CI, and local verification commands. | README confirms corepack enable, corepack prepare pnpm@10.33.0 --activate, and pnpm install. |
| Root-only database ownership wording | packages/db boundary plus root Prisma schema/migrations |
DB architecture and migration docs. | Current state: packages/db owns the server-side Prisma client boundary and command surface, while schema and migrations still physically live under prisma/. |
Broad database provider examples that imply multiple active providers |
PostgreSQL through Prisma, with Supabase only as an example connection target | Setup and deployment docs. | README names PostgreSQL as default and gives a Supabase-style PostgreSQL URL example. Do not imply a second ORM or active non-Postgres provider. |
| Generic Redis requirement for all local development | Optional worker Redis | Quick start and worker docs. | WORKER_ENABLED=true and BULLMQ_REDIS_URL are only required when running the worker; REDIS_URL is a platform-binding fallback. |
| Mail providers such as Postmark when describing active transactional mail | Mailjet transactional mail boundary | Mail setup, architecture, and feature inventory docs. | packages/mail provides the Mailjet adapter and MailProvider contract. Historical provider comparisons may remain if clearly labelled historical. |
Email as a product/marketing feature |
transactional mail for core mail, Email Marketing as plugin candidate |
Feature classification docs. | Core keeps password reset, invitations, and system mail; Email Marketing is a plugin-candidate product surface. |
| Full-boilerplate retention language | Pruned core with plugin candidates and removal candidates | Roadmap, feature inventory, and migration overview docs. | docs/feature-inventory.md is the current classification source. |
| Claims that plugin candidates are already extracted plugins | Plugin-candidate or extraction target | Feature docs, architecture docs, and roadmap docs. | Use plugin-candidate unless the code has actually moved and the slice verifies the new boundary. |
| Claims that remove candidates are supported legacy behavior | Remove-candidate or pruning target | Feature inventory and feature decision docs. | R003 means replacement/removal, not permanent compatibility adapters. |
| Vercel-only or generic hosted deployment wording | Current CI/deployment wording with Gitea, Docker smoke, Coolify, and documented deploy guides | CI and deployment docs. | README confirms Gitea CI, Docker smoke gates, docs/deployment.md, and docs/deployment-porting.md. |
| Registry-push or production-deploy claims in baseline CI | Local Docker smoke validation only | CI docs for this milestone. | The baseline Docker smoke job validates build/configuration without registry login, image push, artifact publishing, deployment, or browser/E2E flows. |
Broad E2E runs by default language |
E2E/browser verification is manual unless explicitly approved | Verification sections and CI docs. | Auto-mode docs should name non-E2E checks and mark browser/E2E as manual. |
apps/web as skeleton-only |
apps/web active web source home |
Workspace and architecture docs. | The physical React Router app source and public assets live under apps/web; root-level app/ is not the active tree. |
apps/api as health-only or fully extracted API |
apps/api runtime-wired Hono API app with temporary proxy exceptions |
API architecture docs. | It owns the canonical /v1 route surface, route-access inventory, and OpenAPI docs. Some /v1 routes proxy web-owned internal handlers until service extraction, so docs must name both boundaries when relevant. |
apps/worker as always-on production worker |
Opt-in worker runtime with queue-group processors | Worker and queue docs. | The worker exits unless WORKER_ENABLED=true; current processors are wired by WORKER_QUEUE_GROUP for general, mail, import, webhook, and plugin workloads. |
packages/config, packages/queue, packages/ui, packages/shared as fully wired runtime packages |
Skeleton-only or contract package surfaces | Package and architecture docs. | Describe their current contract status precisely. |
| Public form/portal/site URLs as tenant-app routes | Separate public URL boundaries | UrlUtils and routing docs. | Do not normalize public slugs or share URLs through internal tenant-app helper wording. |
| Direct tenant-app path concatenation as desired pattern | UrlUtils helper migration target |
Internal routing migration docs. | Use UrlUtils.getModulePath(...) or documented helper plans for internal tenant-app links; public URLs are separate. |
Do-Not-Replace Rules
Do not blindly rename the following public, runtime, package, or historical identifiers while porting documentation:
@toolctrl/*workspace imports and package names, including@toolctrl/config,@toolctrl/db,@toolctrl/mail,@toolctrl/queue, and@toolctrl/ui.- Root package and workspace package names that are confirmed by
package.jsonfiles, includingtoolctrl-boilerplate. - Queue names and runtime strings such as
toolctrl.emailwhen they represent actual queue identifiers or integration contracts. - Environment variable names such as
APP_NAME,SESSION_SECRET,CRYPTO_SECRET,DATABASE_URL,DIRECT_URL,WORKER_ENABLED,BULLMQ_REDIS_URL, andREDIS_URL. - Database, Prisma, migration, and generated-client paths such as
prisma/,prisma/schema/schema.prisma,packages/db/generated/prisma/client, and@toolctrl/db/client.server. - Current local examples that are still accurate, especially
app.localhost:3000andadmin.localhost:3000for the default web loop,api.localhost:3001only for the standalone API runtime,127.0.0.1, seeded users such asadmin@email.com, and sample passwords used only in local seed documentation. - Third-party product names that describe actual dependencies or providers, including Prisma, PostgreSQL, Supabase-style PostgreSQL URLs, Redis, BullMQ, Mailjet, Hono, React Router, Gitea, Docker, Fly.io, Vercel, AWS Lightsail, and Coolify.
- Historical decision/report references, milestone IDs, slice IDs, decision IDs, and files under
.tc/ordocs/feature-decisions/unless a task explicitly updates those records. - Public route shapes and user-facing slugs in examples when the document is explaining public boundaries, for example public forms, portals, sites, help pages, or share links.
Doc-Specific Application Notes
README and quick-start docs
- Keep the quick start centered on Corepack,
pnpm@10.33.0,.env, Prisma setup, seed data, andpnpm dev. - Preserve the distinction between required database settings and optional worker Redis settings.
- Prefer
app.localhost:3000andadmin.localhost:3000for the default web dev loop; useapi.localhost:3001only when the standalone API runtime is started separately. - Do not add browser/E2E execution as an automatic task-level check; label it manual unless a later human-approved verification plan says otherwise.
Architecture and migration docs
- Describe boundary status in present tense and distinguish
runtime-wired,partially runtime-wired,skeleton-only, andsource-onlysurfaces. - Keep the split clear:
apps/web/appowns web source, while root scripts still expose the top-level developer command surface. - For
packages/db, say the package owns the server-side Prisma client boundary and commands, while root Prisma schema and migrations still live underprisma/. - For
packages/mail, say Mailjet is the active transactional mail provider boundary. - For
packages/queue, say BullMQ contracts exist but production wiring is not complete unless a later task proves otherwise.
Feature inventory and pruning docs
- Use the pruned-core classification from
docs/feature-inventory.mdas the source of truth. - Keep Auth, Accounts, Tenants, Admin/Tenant shell, DB/ORM, transactional mail, Billing, i18n, and UI primitives described as core surfaces.
- Describe CRM, Email Marketing, Workflow Engine, Knowledge Base, Portals/Sites, Forms/Surveys, Analytics/Metrics, Events/Webhooks/Audit Trails, No-code Entity Builder, AI/Chatbot/Prompt Builder/MCP, Help Desk, Onboarding Builder, Feature Flags, and Widgets as plugin candidates or review/removal targets according to the inventory.
- Do not imply optional product areas are already extracted unless the relevant code and tests have moved.
Deployment and CI docs
- Align CI prose with the active Gitea pipeline summary from
README.mdand the baseline gate runner. - Treat
pnpm run verify:ci-baselineandpnpm run verify:gitea-ci-workflowas static local verification commands for the baseline CI contract. - Explain Docker smoke as local image/build, web artifact validation, web/api/worker runtime smoke, and compose validation, not registry publishing or production deployment.
- Keep baseline CI wording separate from the deploy workflow: baseline CI Docker smoke does not push or deploy, while
.gitea/workflows/deploy.ymlpublishes full-SHA staging images, updates the staging alias, triggers Coolify staging, and promotes verified staged images to the productionmainalias throughworkflow_dispatch.
Provider and runtime wording
- Use
PostgreSQLandPrismafor the active database stack. - Use
Mailjetfor transactional mail provider wording. - Use
BullMQand Redis only where worker/queue behavior is being described. - Use
React Routerfor the current@toolctrl/webruntime underapps/web/app. - Use
Honofor theapps/apiruntime-wired API app.
Verification
Use these exact non-E2E checks after creating or editing this guide:
test -s docs/porting-guide.md
grep -n "^## Rename/Replace Map" docs/porting-guide.md
grep -n "^## Do-Not-Replace Rules" docs/porting-guide.md
grep -n "^## Verification" docs/porting-guide.md
node -e "const fs=require('fs'); const text=fs.readFileSync('docs/porting-guide.md','utf8'); const banned=/\\b(TODO|TBD|FIXME|XXX)\\b/; if (banned.test(text)) { console.error('unresolved marker in docs/porting-guide.md'); process.exit(1); }"
Manual downstream review: after each documentation slice ports another file, compare remaining legacy terms against this map and the baseline inventory report at .tc/baselines/porting-docs/baseline-health.md. Any remaining old brand, provider, domain, package, database, runtime, or deployment wording should be classified as intentionally protected, historically accurate, or changed in the same documentation slice.