Skip to content

Latest commit

 

History

History
340 lines (275 loc) · 20.3 KB

File metadata and controls

340 lines (275 loc) · 20.3 KB

Stackwright Framework - AI Guide for Agents

Welcome to Stackwright! This is a YAML-driven React application framework that enables rapid development of professional websites and applications through a "content as code" approach. In this guide, you'll find essential knowledge required to be productive in the Stackwright project. For contributor guidelines (branching, commits, testing, changesets), see CONTRIBUTING.md.

Quick Start for New Projects

The fastest way to get started with Stackwright is using launch-stackwright:

Recommended: Full otter raft experience (auto-installs dependencies)

npx launch-stackwright my-site --otter-raft
cd my-site
pnpm dev

Alternative: Manual setup

npx launch-stackwright my-site
cd my-site
pnpm install
pnpm dev

Both set up:

  • ✅ A fully configured Next.js + Stackwright project
  • ✅ The otter raft (AI agents) ready to build your site
  • ✅ MCP server auto-configuration for Code Puppy

See the Otter Raft documentation for how to use the AI agents to build complete sites through conversation.

  • Framework Architecture: To understand the big picture, read:
    • packages/core/src/index.ts: Core framework initialization
    • packages/nextjs/src/components/StackwrightLayout.tsx: App Router root layout (App Router) or StackwrightDocument.tsx (Pages Router, deprecated)
    • packages/themes/src/ThemesProvider.tsx: Theme provider for theme handling
  • Developer Workflows
    • Build: Run pnpm build from the project root
    • Test: Run pnpm test from the project root (runs vitest across all packages)
  • Project Conventions: Note these important patterns that differ from common practices
    • File Organization
      • All source code is in packages directory
      • Core framework components are in src/components of @stackwright/core package
      • Themes are defined in YAML files within the themes directory of the same package
    • Naming Conventions
      • Kebab-case for file names (e.g., main-content-grid.tsx)
      • PascalCase for components (e.g., DynamicPage)

Component Registration

The stackwrightRegistry is a singleton that must be populated before rendering. In Next.js apps:

  • Call registerNextJSComponents() from @stackwright/nextjs in app/layout.tsx (App Router — via a 'use client' Providers component; see @stackwright/nextjs AGENTS.md for the correct pattern) or pages/_app.tsx (Pages Router — deprecated)
  • Call registerDefaultIcons() from @stackwright/icons in the same location
  • Do not rely on module import side effects for registration — it must be explicit

createStackwrightNextConfig() from @stackwright/nextjs should be used in next.config.js instead of manual webpack configuration.

Build System Notes

  • Each package uses tsup for dual-format output (ESM .mjs + CJS .js)
  • Do NOT add "type": "module" to any packages/* package.json. tsup's .mjs/.js extension convention handles format signaling. Adding "type": "module" breaks require() calls in Next.js config files.

Image Co-location Pipeline

Images can be placed alongside their page YAML files in content/pages/. Use ./relative paths in YAML (e.g. src: ./hero.png). These are processed by the stackwright-prebuild script (from @stackwright/build-scripts) which runs before next build and next dev:

  1. Image is copied to public/images/ preserving directory structure
  2. Path is rewritten to /images/... in the processed JSON
  3. getStaticProps reads from the processed JSON — no fs work at render time

Required: Add these hooks to the Next.js app's package.json:

"prebuild": "stackwright-prebuild",
"predev": "stackwright-prebuild"

Without these hooks, co-located images will not be found at runtime.

Internationalization (i18n)

Stackwright supports multi-locale sites via locale-suffix content files and locale-aware prebuild output.

Enable i18n in stackwright.yml:

locales:
  default: en
  supported:
    - en
    - fr
    - de

Page content files — full replacement, not merge:

pages/
  about/
    content.yml        # default locale (en)
    content.fr.yml     # French — entirely replaces content.yml for /fr/about
    content.de.yml     # German
  • content.<locale>.yml must be a complete, valid page YAML — it is NOT merged with content.yml
  • A locale file can be omitted for any page; the default locale is served silently as fallback

Locale site configs (nav, appBar, footer in another language):

  • Place stackwright.fr.yml alongside stackwright.yml in the project root
  • The prebuild outputs public/stackwright-content/_site.fr.json automatically
  • Only fields that differ need overriding — but the file must still be a complete valid siteConfigSchema doc

Prebuild output structure:

public/stackwright-content/
  _site.json          # default locale site config
  _site.fr.json       # French site config (from stackwright.fr.yml)
  _root.json          # default locale root page
  about.json          # default locale /about
  fr/
    _root.json        # French root page
    about.json        # French /fr/about

URL structure: /fr/about serves French content; /about serves the default locale.

Fallback: If content.fr.yml does not exist for a page, getStackwrightPageData falls back to content.yml silently — no 404, no warning.

Static params: generateStackwrightStaticParams() recursively walks the content dir and returns both { slug: ['about'] } and { slug: ['fr', 'about'] } automatically.

Helper utilities (from @stackwright/nextjs):

  • getStackwrightSiteLocales() — reads locales.supported from _site.json; defaults to ['en']
  • parseLocaleFromSlug(slug, supportedLocales) — strips locale prefix from params.slug:
    • ['fr', 'about']{ locale: 'fr', pageSlug: ['about'] }
    • ['about']{ locale: 'en', pageSlug: ['about'] } (default locale)
  • getStackwrightPageData(pageSlug, locale) — reads the locale-specific JSON, falls back silently

MCP tool updates:

  • stackwright_write_page — accepts optional locale param; writes content.<locale>.yml; full schema validation applied
  • stackwright_get_page — accepts optional locale param; falls back to default with a note if locale file absent
  • stackwright_list_pages — shows available locales per page: /about — About Us [en, fr]

Content Type Reference

AGENTS: This table is auto-generated from the live Zod schemas. Run pnpm stackwright -- generate-agent-docs to regenerate. Do NOT edit the content between the markers manually.

The YAML key is the key used inside content_items entries. All types inherit label (required), color (optional), and background (optional) from BaseContent.

YAML key Required fields Optional fields
carousel label (string), heading (string), items (CarouselItem[]) color (string), background (string), autoPlaySpeed (number), infinite (boolean), autoPlay (boolean)
main label (string), heading (TextBlock), textBlocks (TextBlock[]) color (string), background (string), media (MediaItem), graphic_position (left
tabbed_content label (string), heading (TextBlock), tabs (object object
media label (string), src (string) color (string), background (string), alt (string), height (number
timeline label (string), items (TimelineItem[]) color (string), background (string), heading (TextBlock), layout (vertical
icon_grid label (string), icons (IconContent[]) color (string), background (string), heading (TextBlock)
code_block label (string), code (string) color (string), background (string), language (string), lineNumbers (boolean)
feature_list label (string), items (object[]) color (string), background (string), heading (TextBlock), columns (number)
testimonial_grid label (string), items (object[]) color (string), background (string), heading (TextBlock), columns (number)
faq label (string), items (object[]) color (string), background (string), heading (TextBlock)
pricing_table label (string), plans (object[]) color (string), background (string), heading (TextBlock)
alert label (string), variant (info warning
contact_form_stub label (string), email (string) color (string), background (string), heading (TextBlock), description (string), email_subject (string), phone (string), address (string), button_text (string)
form label (string), fields (object[]), action (string) color (string), background (string), heading (TextBlock), description (string), method (GET
text_block label (string), textBlocks (TextBlock[]) color (string), background (string), heading (TextBlock), buttons (ButtonContent[])
grid label (string), columns (GridColumn[]) color (string), background (string), heading (TextBlock), gap (string), stackBelow (number)
collection_list label (string), source (string), layout (default), card (object) columns (number), limit (number), hrefPrefix (string), heading (TextBlock), background (string), color (string)
video label (string), src (string) color (string), background (string), alt (string), height (number
map label (string), center (object), zoom (number) color (string), background (string), markers (object[]), layers (object[]), view (map

Sub-type reference:

Type Fields
TextBlock text (string), textSize (TypographyVariant), textColor? (string)
ButtonContent text (string), textSize (TypographyVariant), textColor? (string), variant (text
MediaItem Discriminated union: type: "media" | type: "icon" | type: "image" | type: "video". type field is required and acts as discriminator.
ImageContent label (string), color? (string), background? (string), src (string), alt? (string), height? (number
IconContent label (string), color? (string), background? (string), src (string), alt? (string), height? (number
CarouselItem title (string), text (string), media (MediaItem), background? (string)
TimelineItem year (string), event (string), yearColor? (string), cardBackground? (string), dotColor? (string)
GridColumn width? (number), content_items (object

TypographyVariant values: h1 h2 h3 h4 h5 h6 subtitle1 subtitle2 body1 body2 caption button overline

Interface Contracts

AGENTS: This table is auto-generated from @stackwright/types. Run pnpm stackwright -- generate-agent-docs to regenerate. Do NOT edit the content between the markers manually.

All interface contracts are defined in @stackwright/types and re-exported from @stackwright/collections, @stackwright/hooks-registry, and @stackwright/scaffold-core for backward compatibility.

Interface / Type Kind Fields / Signature
CollectionProvider interface list(collection, opts?) (Promise), get(collection, slug) (Promise<CollectionEntry
CollectionEntry interface slug (string), [key: string] (unknown)
CollectionListOptions interface limit? (number), offset? (number), sort? (string), filter? (Record<string, unknown>)
CollectionListResult interface entries (CollectionEntry[]), total (number)
ScaffoldHookContext interface targetDir (string), projectName (string), siteTitle (string), themeId (string), packageJson (Record<string, unknown>), dependencyMode ('workspace'
ScaffoldHook interface type (ScaffoldHookType), name (string), handler (HookHandler), priority? (number), critical? (boolean)
HookHandler type (context: ScaffoldHookContext) (Promise
ScaffoldHookType type values ('preScaffold'

Import paths (all equivalent):

  • CollectionProvider@stackwright/types · @stackwright/collections
  • ScaffoldHookContext, ScaffoldHook, HookHandler, ScaffoldHookType@stackwright/types · @stackwright/hooks-registry · @stackwright/scaffold-core

Dark Mode & Color Preferences

Stackwright has first-class dark mode and cookie-based preference persistence:

  • darkColors field in theme YAML — same shape as colors, used when dark mode is active.
  • ThemeProvider manages colorMode ('light' | 'dark' | 'system'), exposes setColorMode() via context. Components read theme.colors and get the resolved palette automatically — zero changes needed in content components.
  • ColorModeScript (from @stackwright/themes) — a blocking <script> placed in <head> that reads the sw-color-mode cookie before React hydrates, preventing flash-of-wrong-theme.
  • StackwrightDocument (from @stackwright/nextjs) — a drop-in _document.tsx that includes ColorModeScript automatically.
  • Cookie utilities (@stackwright/core): getCookie, setCookie, removeCookie — SSR-safe, zero dependencies.
  • Consent utilities (@stackwright/core): getConsentState, setConsentState, hasConsent — IAB TCF categories (necessary, functional, analytics, marketing).

Integration Points and Cross-Component Communication

  • Service Boundaries: No obvious service boundaries — all code resides within the project's monorepo.
  • Data Flows: Data primarily flows from YAML configuration files → prebuild pipeline → JSON → React components via the core framework.
  • External Dependencies
    • Lucide React: Icon library (replaced MUI icons). Static imports registered via registerDefaultIcons().
    • Radix UI: Headless primitives powering @stackwright/ui-shadcn (Tabs, Accordion).
    • Tailwind CSS: Used exclusively by @stackwright/ui-shadcn. Core components use inline style={{}} props — no Tailwind in @stackwright/core.
    • js-yaml: YAML parsing throughout the framework.
    • Zod (v4): Runtime schema validation, JSON schema generation, and MCP tool introspection.
  • Cross-Component Communication
    • Themes and color mode can be changed dynamically: ThemeProvider exposes setTheme and setColorMode via context.
    • Custom events (e.g., onChange) can be registered by child components to interact with parents.

Troubleshooting

See CONTRIBUTING.md for common issues and debugging tips.

References

Page-Level Sidebar Override (navSidebar)

Pages can override the site-wide sidebar defined in stackwright.yml using the navSidebar field in their content.yml.

Resolution order (highest wins):

  1. Page-level navSidebar in content.yml (explicit override)
  2. Site-level sidebar in stackwright.yml (default from Theme Otter)
  3. No sidebar

Use cases:

  • Dashboard pages: navSidebar: null to maximize content width
  • Documentation chapters: different sidebar with section-specific navigation
  • Landing pages: inherit site sidebar from theme

YAML examples:

# Hide sidebar on this page (full-width content)
content:
  navSidebar: null
  content_items:
    - type: main
      label: "dashboard"
      heading:
        text: "Live Dashboard"
        textSize: "h1"

# Override sidebar navigation for this page
content:
  navSidebar:
    navigation:
      - label: "Chapter 1"
        href: "/docs/chapter-1"
      - label: "Chapter 2"
        href: "/docs/chapter-2"
    collapsed: false
  content_items:
    - type: text_block
      label: "chapter-2-content"
      textBlocks:
        - text: "Chapter 2 content here..."
          textSize: "body1"

Otter responsibilities:

  • Theme Otter sets the site-wide sidebar defaults in stackwright.yml
  • Page Otter can add navSidebar overrides in any page's content.yml
  • If Theme Otter chose a sidebar theme, Page Otter inherits it by default (no need to repeat)

Beads Issue Tracker

This project uses bd (beads) for issue tracking. Run bd prime to see full workflow context and commands.

Quick Reference

bd ready              # Find available work
bd show <id>          # View issue details
bd update <id> --claim  # Claim work
bd close <id>         # Complete work

Rules

  • Use bd for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists
  • Run bd prime for detailed command reference and session close protocol
  • Use bd remember for persistent knowledge — do NOT use MEMORY.md files

Architecture in one line: issues live in a local Dolt DB; sync uses refs/dolt/data on your git remote; .beads/issues.jsonl is a passive export. See https://github.com/gastownhall/beads/blob/main/docs/SYNC_CONCEPTS.md for details and anti-patterns.

Session Completion

When ending a work session, you MUST complete ALL steps below. Work is NOT complete until git push succeeds.

MANDATORY WORKFLOW:

  1. File issues for remaining work - Create issues for anything that needs follow-up
  2. Run quality gates (if code changed) - Tests, linters, builds
  3. Update issue status - Close finished work, update in-progress items
  4. PUSH TO REMOTE - This is MANDATORY:
    git pull --rebase
    git push
    git status  # MUST show "up to date with origin"
  5. Clean up - Clear stashes, prune remote branches
  6. Verify - All changes committed AND pushed
  7. Hand off - Provide context for next session

CRITICAL RULES:

  • Work is NOT complete until git push succeeds
  • NEVER stop before pushing - that leaves work stranded locally
  • NEVER say "ready to push when you are" - YOU must push
  • If push fails, resolve and retry until it succeeds