Skip to content

Instantly share code, notes, and snippets.

@wesen
Created June 4, 2026 12:12
Show Gist options
  • Select an option

  • Save wesen/8e69c924bb1bea3f0cf547a0a1dd2ee7 to your computer and use it in GitHub Desktop.

Select an option

Save wesen/8e69c924bb1bea3f0cf547a0a1dd2ee7 to your computer and use it in GitHub Desktop.
PROJ-GUIDE-001: Project Creation Guideline + Diary
title Investigation Diary
doc_type reference
status active
intent long-term
topics
project-setup
react
go
glazed
storybook
rtk-query
design-system
intern-guide
ticket PROJ-GUIDE-001
created 2026-06-03

Diary

Goal

Record the investigation and writing process for the Project Creation Guideline reference document, which describes the full-stack project patterns used across go-go-golems/TTC repos.

Step 1: Repository Discovery and Evidence Gathering

I surveyed three repositories to extract the common project creation patterns: the current TTC design-system repo, the Pyxis venue management repo, and the go-template scaffold repo. The goal was to gather concrete evidence for every claim in the guideline document.

Prompt Context

User prompt (verbatim): "Create a new docmgr ticket for creating a project creation guideline reference document. First, we analyze the current structure and similar repos like /home/manuel/code/wesen/2026-04-23--pyxis Analyze the current repository and document the guidelines for the react setup: storybook, react, redux, rtk-query for the go: glazed verbs, go-template setup from ~/code/wesen/go-go-golems/go-template"

Assistant interpretation: Create a docmgr ticket and write an exhaustive intern-ready guideline document covering the full-stack patterns (React/Storybook/Redux/RTK Query on the frontend, Glazed verbs/go-template on the backend) by analyzing the TTC design system repo and the Pyxis repo as evidence.

Inferred user intent: Produce a reusable reference that a new intern can follow to create a new project in the same style, with enough technical depth and file references that they don't need to guess.

What I did

  • Created docmgr ticket PROJ-GUIDE-001 with two documents (design doc + diary)
  • Explored the full file tree of all three repos
  • Read key configuration files: package.json, pnpm-workspace.yaml, GUIDELINES.md, go.mod, Makefile, .goreleaser.yaml, lefthook.yml, .golangci.yml, AGENT.md, .devctl.yaml, store.ts, publicApi.ts, vite.config.ts, .storybook/main.ts
  • Read Go entry points: pyxis/cmd/pyxis/main.go, pyxis/cmd/pyxis/cmds/serve.go, ttc-garden-chat/main.go
  • Read the glazed verb pattern from the serve command (ServeCommand with CommandDescription, fields, sections, RunIntoGlazeProcessor)
  • Read the go-template scaffold (cmd/XXX/main.go, pkg/logcopter.go, logcopter_generate.go)
  • Read the dmeta component manifest system from TTC

Why

Need evidence-backed claims for every pattern documented in the guideline.

What worked

  • Finding the consistent pnpm monorepo pattern across TTC and Pyxis
  • Identifying the glazed verb pattern as the standard Go command abstraction
  • Discovering that go-template provides the scaffold with logcopter, goreleaser, CI, and lefthook
  • Confirming RTK Query usage in Pyxis and Redux Toolkit setup in TTC

What didn't work

  • N/A (discovery phase, no failures)

What I learned

  • The go-template repo uses XXX as a placeholder name that must be replaced everywhere
  • The glazed verb pattern is consistent: define a struct with CommandDescription, add fields/sections, implement RunIntoGlazeProcessor or RunIntoWriter
  • Both Pyxis and TTC use makeStore() factory pattern for Redux stores
  • The dmeta system in TTC is a design-metadata-driven code generation pipeline for React components

What was tricky to build

  • Understanding the exact boundary between generated (dmeta) components and hand-written promoted components requires reading both the generated manifest and the promotion state

What warrants a second pair of eyes

  • The glazed verb lifecycle: whether RunIntoGlazeProcessor vs RunIntoWriter is the right choice for new commands
  • The go-template replacement strategy: all XXX strings must be replaced, but some are in CI secrets/tokens that can't be found by simple grep

What should be done in the future

  • Cross-reference the glazed help system documentation for the help export command
  • Document the devctl plugin protocol in more detail

Code review instructions

  • Read ttmp/2026/06/03/PROJ-GUIDE-001--project-creation-guideline-reference-document/design-doc/01-project-creation-guideline.md
  • Verify that every file reference actually exists and the described patterns match the source code

Technical details

  • Ticket path: ttmp/2026/06/03/PROJ-GUIDE-001--project-creation-guideline-reference-document
  • Design doc path: design-doc/01-project-creation-guideline.md
  • Diary path: reference/01-investigation-diary.md

Step 2: Doctor Validation and reMarkable Upload

Fixed the glazed vocabulary entry (it wasn't persisted in the YAML initially, re-added). Removed cross-repo related files from the ticket index (Pyxis and go-template files are outside this repo, so doctor reported them as missing). Doctor passes cleanly after fixes.

Uploaded the design doc + diary bundle to reMarkable at /ai/2026/06/03/PROJ-GUIDE-001. Dry-run confirmed the correct file list and output name. Upload succeeded and was verified via remarquee cloud ls.

Prompt Context

User prompt (verbatim): (see Step 1)

Assistant interpretation: Commit work and upload to reMarkable.

Inferred user intent: Persist the work to git and deliver to reMarkable for reading.

What I did

  • Added glazed vocab entry (again, first attempt didn't persist)
  • Removed cross-repo related file entries from ticket index
  • Ran docmgr doctor — passes cleanly
  • remarquee upload bundle --dry-run — confirmed correct files
  • remarquee upload bundle — uploaded successfully
  • remarquee cloud ls — verified upload at /ai/2026/06/03/PROJ-GUIDE-001
  • Committed all changes to git

Why

Need to validate and deliver before considering the ticket complete.

What worked

  • reMarkable upload on first try
  • Doctor passes after vocab fix

What didn't work

  • First docmgr vocab add for glazed didn't persist to YAML; had to re-run

What I learned

  • docmgr vocab add can silently fail to persist if there's a YAML formatting issue

What was tricky to build

  • N/A

What warrants a second pair of eyes

  • N/A

What should be done in the future

  • Consider adding cross-repo related file support to docmgr (currently files outside the repo root can't be verified by doctor)

Code review instructions

  • Verify remarquee cloud ls /ai/2026/06/03/PROJ-GUIDE-001 --long --non-interactive shows the uploaded PDF

Technical details

  • Commit hash: (pending)
  • reMarkable path: /ai/2026/06/03/PROJ-GUIDE-001
  • Bundle name: PROJ-GUIDE-001 Project Creation Guideline.pdf
title Project Creation Guideline
doc_type design-doc
status active
intent long-term
topics
project-setup
react
go
glazed
storybook
rtk-query
design-system
intern-guide
ticket PROJ-GUIDE-001
created 2026-06-03

Project Creation Guideline

Executive Summary

This document is the reference guide for creating new full-stack projects in the go-go-golems ecosystem. It describes the two major halves of any project — the React/TypeScript frontend and the Go backend — and the standard tooling, file layout, and patterns that connect them. Every pattern here is backed by concrete evidence from two production projects: the TTC Garden Assistant (2026-05-27--ttc-design-system) and Pyxis (2026-04-23--pyxis), plus the go-template scaffold (go-go-golems/go-template).

A new intern should be able to read this document, copy the go-template scaffold, set up the pnpm workspace, wire the glazed verb, configure Storybook, and have a running full-stack project by the end of the day.


Problem Statement and Scope

New projects in this ecosystem follow a specific stack and set of conventions, but these conventions are distributed across multiple repositories, READMEs, and AGENT.md files. There is no single document that walks through the full project creation process end-to-end, from go-template scaffold to running Storybook and backend.

This guideline covers:

  1. Go backend scaffolding from go-template, including CI, linting, release, and the Glazed verb command pattern.
  2. React frontend setup with pnpm monorepo, Vite, Storybook, Redux Toolkit, and RTK Query.
  3. Design system architecture with the atomic design layer stack (tokens → foundation → atoms → molecules → organisms → pages).
  4. Full-stack integration patterns: Go serving the embedded SPA, WebSocket chat, and devctl-based development workflow.
  5. Dmeta code generation pipeline for design-metadata-driven component scaffolding.

Part I: Go Backend — The go-template Scaffold

1.1 Overview

Every new Go project starts from the go-template repository at ~/code/wesen/go-go-golems/go-template. This is a minimal scaffold that ships with:

  • A placeholder binary entry point at cmd/XXX/main.go
  • Package scaffolding at pkg/doc.go and pkg/logcopter.go
  • A go:generate directive for structured logging via logcopter
  • A Makefile with standard targets (lint, test, build, goreleaser)
  • GitHub Actions CI (push, release, lint, security scanning)
  • GoReleaser configuration for cross-platform builds, Homebrew tap, and Fury.io deb/rpm publishing
  • Lefthook pre-commit/pre-push hooks
  • golangci-lint configuration

1.2 File Layout

The go-template scaffold has this structure:

go-template/
├── cmd/XXX/main.go          # Binary entry point (empty func main)
├── pkg/
│   ├── doc.go               # Package documentation
│   └── logcopter.go         # Generated structured logger (DO NOT EDIT)
├── logcopter_generate.go    # go:generate directive for logcopter-gen
├── go.mod                   # Module: github.com/go-go-golems/XXX
├── Makefile                 # Standard build/test/lint/release targets
├── .goreleaser.yaml         # Cross-platform release config
├── .golangci.yml            # Linter configuration
├── .golangci-lint-version   # Pinned linter version
├── lefthook.yml             # Git hooks (pre-commit lint+test, pre-push release)
├── AGENT.md                 # Agent guidelines (build commands, conventions)
├── LICENSE                  # MIT
├── README.md                # ASCII art title page
└── .github/workflows/
    ├── push.yml             # CI: test, logcopter-check, go generate, git diff
    ├── release.yaml         # Multi-OS goreleaser with GPG signing, Homebrew, Fury
    ├── lint.yml             # golangci-lint
    ├── codeql-analysis.yml  # CodeQL security
    ├── dependency-scanning.yml
    └── secret-scanning.yml

1.3 Creating a New Project from the Template

Step 1: Copy the scaffold.

cp -r ~/code/wesen/go-go-golems/go-template ~/code/wesen/YYYY-MM-DD--my-project
cd ~/code/wesen/YYYY-MM-DD--my-project
rm -rf .git
git init

Step 2: Replace all XXX placeholders.

The template uses XXX as a placeholder for the binary/module name. You must replace every occurrence:

# Replace in all tracked files (be careful with .git/ and binary files)
grep -r "XXX" --include='*.go' --include='*.yaml' --include='*.yml' --include='*.md' -l .
# Then manually edit each file to replace XXX with your project name

Specific replacements needed:

File Placeholder Replace with
go.mod module github.com/go-go-golems/XXX module github.com/go-go-golems/my-project
cmd/XXX/ Directory name cmd/my-project/
.goreleaser.yaml project_name: XXX, binary: XXX, main: ./cmd/XXX Your project name
Makefile XXX_BINARY, install: target Your project name
logcopter_generate.go -area-prefix go-go-golems.XXX, -strip-prefix github.com/go-go-golems/XXX Your module path
pkg/logcopter.go Package("go-go-golems.XXX.pkg") Your package path
.goreleaser.yaml description, homepage, brews.name Your project info

Step 3: Initialize and verify.

go mod tidy
go generate ./...
go build ./...
go test ./...
make lint

1.4 The Logcopter System

Logcopter is the structured logging framework used across go-go-golems projects. It works via code generation:

  1. Directive (logcopter_generate.go):

    package XXX
    //go:generate go tool logcopter-gen -area-prefix go-go-golems.XXX -strip-prefix github.com/go-go-golems/XXX ./pkg/...
  2. Generated logger (pkg/logcopter.go — DO NOT EDIT):

    // Code generated by logcopter-gen; DO NOT EDIT.
    package pkg
    import logcopter "github.com/go-go-golems/logcopter/pkg/logcopter"
    var log = logcopter.Package("go-go-golems.XXX.pkg")
  3. CI verification: The push workflow runs make logcopter-check to ensure generated loggers are up to date, and git diff --exit-code to verify no stale generated files.

The logcopter system creates a hierarchical logger per package. The -area-prefix flag sets the top-level area (e.g., go-go-golems.pyxis), and -strip-prefix removes the module path from package names, resulting in structured log fields like area=pyxis.pkg.db or area=pyxis.pkg.server.

1.5 The Makefile

The go-template Makefile provides these standard targets:

Target Purpose
lint Run golangci-lint
lintmax Run golangci-lint with higher issue limit
test Run go test ./...
build go generate + go build
logcopter-generate Regenerate logcopter loggers
logcopter-check Verify logcopter loggers are up to date
goreleaser Run GoReleaser (snapshot by default)
tag-major/minor/patch Bump semver tags using svu
release Push tags + publish to Go proxy
bump-go-go-golems Update all go-go-golems dependencies to latest
install Build and copy binary to PATH

Key convention: GOWORK=off is used on all targets to avoid interference from Go workspace mode.

1.6 GoReleaser Configuration

The .goreleaser.yaml is set up for:

  • Two build targets: Linux (amd64, arm64 with cross-compilers) and Darwin (amd64, arm64)
  • CGO_ENABLED=1: Required for SQLite (mattn/go-sqlite3) used in most projects
  • GPG signing: Signs checksums with the GO_GO_GOLEMS_SIGN_KEY
  • Homebrew tap: Publishes to go-go-golems/homebrew-go-go-go
  • Fury.io: Publishes deb/rpm packages
  • Split release: The release workflow runs goreleaser on Linux and macOS runners separately, then merges the artifacts

The release workflow (release.yaml) requires these GitHub secrets:

  • GITHUB_TOKEN
  • GORELEASER_KEY (for goreleaser-pro)
  • GO_GO_GOLEMS_SIGN_KEY / GO_GO_GOLEMS_SIGN_PASSPHRASE
  • HOMEBREW_TAP_TOKEN
  • FURY_TOKEN

1.7 Lefthook Git Hooks

# lefthook.yml
pre-commit:
  commands:
    lint:
      glob: '*.go'
      run: make lint
    test:
      glob: '*.go'
      run: make test
  parallel: true

pre-push:
  commands:
    release:
      run: make goreleaser
    lint:
      run: make lint
    test:
      run: make test
  parallel: true

This ensures that every commit passes lint and tests, and every push verifies a clean release build.

1.8 golangci-lint Configuration

The .golangci.yml (version 2 format) enables:

  • Default linters: errcheck, govet, ineffassign, staticcheck, unused
  • Additional linters: exhaustive, nonamedreturns, predeclared
  • Formatter: gofmt
  • Exclusions: Specific staticcheck deprecation warnings for legacy glazed APIs

Part II: Go Backend — The Glazed Verb Pattern

2.1 Overview

The Glazed framework is the command abstraction layer used across all go-go-golems CLI applications. It provides a declarative way to define commands with typed flags, argument parsing, output formatting, and help system integration. Every CLI command in these projects is a "Glazed verb."

The key packages are:

  • github.com/go-go-golems/glazed/pkg/cmds — Command descriptions and interfaces
  • github.com/go-go-golems/glazed/pkg/cmds/fields — Declarative flag definitions
  • github.com/go-go-golems/glazed/pkg/cmds/values — Parsed flag value access
  • github.com/go-go-golems/glazed/pkg/cmds/schema — Schema-based settings sections
  • github.com/go-go-golems/glazed/pkg/cli — Cobra bridge (BuildCobraCommandFromCommand)
  • github.com/go-go-golems/glazed/pkg/help — Help system with markdown topics
  • github.com/go-go-golems/glazed/pkg/middlewares — Output processing pipeline

2.2 The Verb Lifecycle

Every Glazed verb follows this lifecycle:

Define CommandDescription
       ↓
Register with Cobra via cli.BuildCobraCommandFromCommand()
       ↓
Cobra parses CLI args → values.Values
       ↓
Decode sections into typed settings struct
       ↓
Execute: RunIntoGlazeProcessor() or RunIntoWriter()
       ↓
Return output through Glaze middleware pipeline or direct writer

2.3 Anatomy of a Glazed Verb

Here is the canonical pattern, extracted from Pyxis's serve.go and TTC's main.go:

// 1. Define the command struct
type ServeCommand struct {
    *cmds.CommandDescription
    // Optional: additional fields like embedded FS
}

// 2. Define the settings struct (decoded from parsed flags)
type ServeSettings struct {
    Bind    string `glazed:"bind"`
    DBURL   string `glazed:"db-url"`
    // ... more flags with glazed struct tags
}

// 3. Constructor: build CommandDescription with flags and sections
func NewServeCommand() (*ServeCommand, error) {
    glazedSection, err := settings.NewGlazedSchema()
    if err != nil {
        return nil, err
    }
    loggingSection, err := logging.NewLoggingSection()
    if err != nil {
        return nil, err
    }

    cmdDesc := cmds.NewCommandDescription(
        "serve",
        cmds.WithShort("Start the HTTP server"),
        cmds.WithLong(`...detailed description...`),
        cmds.WithFlags(
            fields.New("bind", fields.TypeString,
                fields.WithDefault("0.0.0.0:8080"),
                fields.WithHelp("Address to bind"),
            ),
            fields.New("db-url", fields.TypeString,
                fields.WithDefault("postgres://..."),
                fields.WithHelp("PostgreSQL connection string"),
            ),
            // ... more flags
        ),
        cmds.WithSections(glazedSection, loggingSection),
    )

    return &ServeCommand{CommandDescription: cmdDesc}, nil
}

// 4. Implement the execution method
// Option A: RunIntoGlazeProcessor — for commands that produce structured output
func (c *ServeCommand) RunIntoGlazeProcessor(
    ctx context.Context,
    vals *values.Values,
    gp middlewares.Processor,
) error {
    s := &ServeSettings{}
    if err := vals.DecodeSectionInto(schema.DefaultSlug, s); err != nil {
        return err
    }
    // ... business logic using s.Bind, s.DBURL, etc.
    return nil
}

// Option B: RunIntoWriter — for commands that write directly (e.g., server startup)
func (c *Command) RunIntoWriter(
    ctx context.Context,
    parsed *values.Values,
    _ io.Writer,
) error {
    return webchatcmd.Run(ctx, parsed, c.staticFS)
}

2.4 Flag Definition with fields.New

The fields.New() function creates declarative flag definitions. Each flag specifies:

  • Name: The CLI flag name (e.g., "bind")
  • Type: One of fields.TypeString, fields.TypeBool, fields.TypeInteger, etc.
  • Options: fields.WithDefault(), fields.WithHelp(), fields.WithRequired()

Environment variable convention: Flags typically have corresponding environment variables with the project prefix (e.g., PYXIS_BIND, PYXIS_DATABASE_URL). The envOr() helper provides fallback:

func envOr(key, fallback string) string {
    value := strings.TrimSpace(os.Getenv(key))
    if value == "" {
        return fallback
    }
    return value
}

2.5 Sections: Composable Settings Groups

Sections allow composable groups of flags to be added to any command. The most common sections are:

Section Package Purpose
GlazedSchema glazed/pkg/settings Output format, fields, filters, rename, template
LoggingSection glazed/pkg/cmds/logging Log level, log format
ClientValueSection geppetto/pkg/steps/ai/settings AI/LLM client configuration
ProfileSettingsSection pinocchio/pkg/cmds/profilebootstrap Pinocchio profile registry

Sections are added via cmds.WithSections(...). Their flags are namespaced under the section slug (e.g., --glazed-output, --log-level).

2.6 Wiring into Cobra

The cli.BuildCobraCommandFromCommand() function converts a Glazed CommandDescription into a cobra.Command. The main.go pattern is:

func main() {
    root := &cobra.Command{
        Use: "my-project",
        PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
            return logging.InitLoggerFromCobra(cmd)
        },
        SilenceUsage:  true,
        SilenceErrors: true,
    }

    // Wire help system
    helpSystem := help.NewHelpSystem()
    help_cmd.SetupCobraRootCommand(helpSystem, root)

    // Wire logging
    if err := logging.AddLoggingSectionToRootCommand(root, "my-project"); err != nil {
        log.Fatal().Err(err).Msg("failed to add logging section")
    }

    // Create and register commands
    c, err := NewServeCommand()
    if err != nil {
        log.Fatal().Err(err).Msg("failed to create serve command")
    }
    cobraCmd, err := cli.BuildCobraCommandFromCommand(c)
    if err != nil {
        log.Fatal().Err(err).Msg("failed to build cobra command")
    }
    root.AddCommand(cobraCmd)

    root.ExecuteContext(context.Background())
}

2.7 Help System

Glazed provides an embedded help system for markdown documentation. The pattern is:

  1. Create embedded markdown files under doc/ (with YAML frontmatter)
  2. Load them with help.LoadSectionsFromFS()
  3. Register with help_cmd.SetupCobraRootCommand(helpSystem, rootCmd)
  4. Users can run my-project help <topic> to read docs

The help system supports section types: GeneralTopic, Example, Application, Tutorial.


Part III: React Frontend — Project Setup

3.1 pnpm Monorepo Structure

Both TTC and Pyxis use a pnpm workspace monorepo layout. The pattern is consistent:

web/
├── package.json           # Root workspace scripts
├── pnpm-workspace.yaml    # packages: ['packages/*']
├── pnpm-lock.yaml         # Lock file (checked in)
└── packages/
    ├── <project>-components/   # Reusable component library + Storybook
    └── <project>-user-site/    # Public-facing React SPA (or app package)

The pnpm-workspace.yaml is always:

packages:
  - 'packages/*'

The root package.json delegates to workspace packages:

{
  "name": "ttc-web-workspace",
  "private": true,
  "scripts": {
    "dev": "pnpm --filter ttc-garden-assistant dev",
    "build": "pnpm --filter ttc-garden-assistant build",
    "storybook": "pnpm --filter ttc-garden-assistant storybook",
    "build-storybook": "pnpm --filter ttc-garden-assistant build-storybook",
    "typecheck": "pnpm --filter ttc-garden-assistant typecheck",
    "clean": "pnpm --filter ttc-garden-assistant clean"
  },
  "engines": {
    "node": ">=20.0.0",
    "pnpm": ">=9.0.0"
  },
  "packageManager": "pnpm@10.15.1"
}

Key convention: Use pnpm --filter <package-name> to run scripts in specific workspace packages. The --filter flag is the standard way to target a package.

3.2 Package Structure: Component Library

The component library package contains:

packages/<project>-components/
├── src/
│   ├── atoms/            # Small controls (Button, Icon, Chip, Input, Tag...)
│   ├── molecules/        # Composed patterns (Card, Field, SectionHeader...)
│   ├── organisms/        # Domain widgets (TopBar, Modal, ShowGrid...)
│   ├── public/           # Public-facing specific components
│   ├── tokens/           # CSS custom properties + TS constants
│   ├── mocks/            # MSW handlers for Storybook
│   └── index.ts          # Barrel export
├── .storybook/
│   ├── main.ts           # Storybook configuration
│   └── preview.tsx       # Global decorators, viewport, backgrounds
├── vite.config.ts        # Vite build config
├── tsconfig.json
└── package.json

3.3 Package Structure: Application Site

The application site package contains:

packages/<project>-user-site/
├── src/
│   ├── api/              # RTK Query API layer
│   │   ├── endpoints.ts   # URL path constants
│   │   ├── publicApi.ts   # RTK Query API definition
│   │   ├── hooks.ts       # Exported hooks
│   │   ├── errors.ts      # Error handling
│   │   └── types.ts       # API types
│   ├── pages/            # Route-level page components
│   │   ├── AboutPage/
│   │   │   ├── Page.tsx
│   │   │   ├── Page.css
│   │   │   ├── Page.stories.tsx
│   │   │   └── index.ts
│   │   └── index.ts       # Page barrel export
│   ├── components/       # App-specific components (Layout, ErrorBoundary, Seo)
│   ├── styles/           # Global CSS
│   ├── store.ts          # Redux store configuration
│   ├── App.tsx           # Router configuration
│   └── main.tsx          # Entry point
├── .storybook/
│   ├── main.ts
│   └── preview.tsx
├── vite.config.ts
├── .env.example
└── package.json

3.4 Vite Configuration

The Vite configuration follows a standard pattern. Key aspects:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  base: './',
  plugins: [react()],
  resolve: {
    dedupe: ['react', 'react-dom', 'react-redux'],  // Prevent duplicate React
  },
  server: {
    host: '0.0.0.0',
    port: 3008,
    proxy: {
      // Proxy API requests to Go backend during development
      '/api/chat': {
        target: process.env.TTC_CHAT_BACKEND_TARGET ?? 'http://localhost:8080',
        changeOrigin: true,
        ws: true,  // WebSocket support
      },
    },
  },
  test: {
    environment: 'jsdom',
    setupFiles: './src/test/setup.ts',
  },
});

Important: The dedupe option is critical for React — without it, pnpm workspace packages can end up with multiple React instances, causing hooks errors.

3.5 Storybook Configuration

Storybook is configured with Vite as the builder:

// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  docs: { autodocs: 'tag' },
  staticDirs: ['../public'],
};

export default config;

Key addons:

  • addon-essentials — Controls, Actions, Viewport, Backgrounds, Docs, Measure, Outline
  • addon-a11y — Accessibility auditing
  • addon-interactions — Interaction testing

The autodocs: 'tag' setting auto-generates documentation for stories tagged with autodocs.


Part IV: React Frontend — Redux Toolkit and RTK Query

4.1 Store Configuration Pattern

Both projects use a makeStore() factory function. This is important for testing (each test gets a fresh store) and for Storybook (each story can have its own store).

Pyxis pattern (with RTK Query):

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { publicApi } from './api/publicApi';

export function makeStore() {
  return configureStore({
    reducer: {
      [publicApi.reducerPath]: publicApi.reducer,
    },
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware().concat(publicApi.middleware),
  });
}

export const store = makeStore();
setupListeners(store.dispatch);  // Enable refetchOnFocus/refetchOnReconnect

export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

TTC pattern (minimal, no API slice yet):

// store.ts
import { configureStore } from '@reduxjs/toolkit';

export function makeStore() {
  return configureStore({ reducer: {} });
}

export const store = makeStore();
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore['getState']>;
export type AppDispatch = AppStore['dispatch'];
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

The typed hooks (useAppDispatch, useAppSelector) are always exported for type safety.

4.2 RTK Query API Definition

RTK Query is used for data fetching with caching, invalidation, and optimistic updates. The standard pattern from Pyxis:

// api/publicApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const API_BASE_URL = import.meta.env.VITE_API_URL ?? '';

export const publicApi = createApi({
  reducerPath: 'publicApi',
  baseQuery: fetchBaseQuery({
    baseUrl: API_BASE_URL,
    prepareHeaders: (headers) => {
      headers.set('Content-Type', 'application/json');
      return headers;
    },
  }),
  tagTypes: ['Show', 'Archive', 'Settings', 'Submission', 'ExternalEvent'],
  endpoints: (builder) => ({
    getUpcomingShows: builder.query<Show[], void>({
      query: () => endpoints.shows,
      transformResponse: (response: unknown) => {
        const list = fromJson(ShowListSchema, response as any);
        return list.shows;
      },
      providesTags: (result) =>
        result
          ? [
              ...result.map((show) => ({ type: 'Show' as const, id: show.id })),
              { type: 'Show' as const, id: 'LIST' },
            ]
          : [{ type: 'Show' as const, id: 'LIST' }],
    }),

    submitBooking: builder.mutation<BookingConfirmation, BookingFormData>({
      query: (body) => ({
        url: endpoints.submissions,
        method: 'POST',
        body: toJson(BookingFormDataSchema, body),
      }),
      invalidatesTags: [{ type: 'Submission', id: 'LIST' }],
    }),
  }),
});

// Export auto-generated hooks
export const {
  useGetUpcomingShowsQuery,
  useGetShowQuery,
  useSubmitBookingMutation,
  // ...
} = publicApi;

Key patterns:

  1. Tag-based caching: Define tagTypes at the API level, use providesTags on queries and invalidatesTags on mutations.
  2. transformResponse: Always validate API responses with schema parsers (e.g., fromJson) before returning to components.
  3. Environment variable: VITE_API_URL configures the backend URL; defaults to empty string (same-origin).
  4. Auto-generated hooks: RTK Query exports typed hooks (useGetXxxQuery, usePostXxxMutation) automatically.

4.3 Endpoint URL Constants

Endpoints are defined as plain string constants:

// api/endpoints.ts
const API_PREFIX = '/api/v1';

export const endpoints = {
  shows: `${API_PREFIX}/shows`,
  show: (id: number) => `${API_PREFIX}/shows/${id}`,
  archive: `${API_PREFIX}/archive`,
  archiveStats: `${API_PREFIX}/archive/stats`,
  settings: `${API_PREFIX}/settings`,
  submissions: `${API_PREFIX}/submissions`,
  externalEvents: `${API_PREFIX}/external-events`,
};

This separation makes it easy to change URL patterns without touching the API logic.


Part V: Design System Architecture

5.1 Atomic Design Layer Stack

The TTC design system follows a strict layered architecture:

tokens → foundation → atoms → molecules → organisms → pages → containers

Each layer has clear ownership:

Layer Directory Owns
Tokens src/styles/tokens.css Brand colors, typography roles, spacing, radii, borders, shadows, semantic aliases (all prefixed --ttc-*)
Foundation src/components/foundation Text primitives: Text, Heading, Caption, code/status text, accessible text
Atoms src/components/atoms Small controls: Button, Icon, Chip
Molecules src/components/molecules Reusable composed UI patterns: SectionHeader, ProductCard, EditorialCard, FilterBar
Organisms src/components/organisms Product/domain widgets: TopMatchesWidget, ShoppingPlanSummaryWidget, CareCalendarWidget
Pages src/components/pages Static page/story composition: TreeCenterHomePage
Containers src/features Runtime/container wiring, side effects, chat overlay

5.2 The CSS Rules

From web/GUIDELINES.md:

Feature CSS may define:

  • Positioning
  • Grid/flex anatomy
  • Scroll regions
  • Responsive container sizing
  • Widget/message row placement

Feature CSS must NOT define:

  • One-off typography
  • Hard-coded colors
  • Ad-hoc shadows
  • Bespoke button styles
  • Product-card/widget internals (when foundation/atoms/organisms already provide those)

Rule: Use design tokens (--ttc-*) whenever CSS is unavoidable. Do not solve visual mismatches by adding large feature-specific CSS blocks. Prefer moving repeated visual decisions into the correct layer.

5.3 Component File Convention

Each component follows a consistent file structure:

ComponentName/
├── ComponentName.tsx            # React component
├── ComponentName.module.css     # Scoped CSS (CSS Modules)
├── ComponentName.stories.tsx    # Storybook stories
├── index.ts                     # Barrel export
├── PROMOTION.md                 # (if promoted from dmeta) Promotion notes
└── README.md                    # (if generated) Description from dmeta

For generated (dmeta) components, files are named ComponentName.generated.tsx, ComponentName.generated.module.css, ComponentName.generated.stories.tsx, etc.

5.4 The Dmeta Code Generation System

The TTC project uses a design-metadata-driven code generation pipeline called dmeta. This system generates React components from YAML metadata specifications.

The pipeline works as follows:

dmeta-ir/ (YAML metadata)
       ↓
dmeta code generator
       ↓
src/generated/dmeta-widgets/ (generated React components)
       ↓
Promotion process (select components to hand-customize)
       ↓
src/components/ (promoted, hand-edited components)

Generated output per component:

generated/dmeta-widgets/<kind>/<ComponentName>/
├── ComponentName.generated.tsx            # Generated React component
├── ComponentName.generated.module.css      # Generated CSS
├── ComponentName.generated.stories.tsx     # Generated Storybook story
├── ComponentName.generated.types.ts        # Generated TypeScript types
├── ComponentName.metadata.json             # Component metadata
├── README.md                               # Auto-generated description
└── index.ts                                # Barrel export

The manifest (dmeta.generated-manifest.json) is a single JSON file listing all generated components with their template IDs, paths, semantics (domain types, representations, actions), and promotion status.

Promotion state is tracked in src/dmeta/promotionState.ts:

export type DmetaPromotionState = 'generated' | 'promoted' | 'deprecated';

export const dmetaPromotionState: Record<string, { state: DmetaPromotionState; notes: string }> = {
  'ttc.top_matches.v1': { state: 'promoted', notes: 'Hand-tuned layout' },
  'ttc.care_calendar.v1': { state: 'generated', notes: '' },
  // ...
};

When a component is "promoted", it moves from src/generated/ to src/components/ and becomes hand-editable. The generated version is kept for reference but is no longer imported.


Part VI: Full-Stack Integration

6.1 Go Serving the Embedded SPA

Both Pyxis and TTC use Go's embed package to serve the built frontend from the Go binary. The pattern:

//go:embed static
var staticFS embed.FS

// In main.go:
staticSubFS, err := fs.Sub(staticFS, "static")
cobra.CheckErr(errors.Wrap(err, "open embedded static fs"))

The frontend is built by a shell script invoked via go:generate:

//go:generate bash ../../scripts/build-web.sh

The build script (backend/scripts/build-web.sh) typically:

  1. cd into the web/ directory
  2. Run pnpm install && pnpm build
  3. Copy the Vite output (dist/) into backend/static/

6.2 The Webapp Server Pattern

The Go HTTP server uses net/http.ServeMux (Go 1.22+ new handler syntax). No third-party router (chi, gin, echo) is used. The pattern from TTC:

internal/webapp/
├── server.go       # Server struct with ServeMux
├── routes.go       # Route registration
├── root.go         # Root handler (SPA fallback)
└── config.go       # Configuration loading

Routes are registered like:

mux.HandleFunc("GET /api/v1/sessions", h.handleListSessions)
mux.HandleFunc("POST /api/v1/sessions", h.handleCreateSession)
mux.HandleFunc("GET /api/chat", h.handleWebSocket)  // WebSocket upgrade
mux.HandleFunc("/", h.serveSPA)                      // SPA fallback

6.3 WebSocket Chat Architecture (TTC-specific)

The TTC Garden Assistant uses a WebSocket-based chat architecture:

Browser
  ↓ WebSocket /api/chat
AppServer (routes_ws.go)
  ↓
Session Manager (routes_sessions.go)
  ↓
Runtime Engine (mockruntime or realruntime)
  ↓
Frontend Tools (routes_frontend_tools.go)
  ↓
Widget Publisher (ttcwidgets/publish.go)
  ↓
WebSocket response with widget payloads

The system supports two runtime modes:

  • mock: Deterministic responses for development and testing
  • provider: Real LLM integration via Pinocchio profiles

6.4 Devctl Development Workflow

Both projects use devctl for unified development environment management. The .devctl.yaml file defines profiles and plugins:

profile:
  active: ttc-garden-chat

profiles:
  ttc-garden-chat:
    display_name: TTC Garden Chat
    plugins:
      - ttc-garden-chat
    env:
      TTC_CHAT_RUNTIME: mock

plugins:
  - id: ttc-garden-chat
    path: python3
    args:
      - ./plugins/ttc_garden_chat.py
    priority: 10

Devctl manages:

  • Starting the Go backend
  • Starting the Vite frontend dev server
  • Setting environment variables
  • Health checking and log management

Commands:

devctl up       # Start all services
devctl status   # Check running services
devctl down     # Stop all services
devctl logs     # View service logs

Part VII: Existing Documentation That Informs This Guide

The following documents were found during the investigation that contribute to or overlap with this guideline:

Document Repo Relevance
web/GUIDELINES.md TTC CSS layer rules, design system layer ownership, chat overlay rules
AGENT.md go-template Go build commands, project structure, web/backend guidelines
docs/howtos/how-to-work-on-the-ui.md TTC Step-by-step UI development workflow
docs/howtos/how-to-test.md TTC Testing conventions
docs/playbooks/ttc-garden-chat-devctl.md TTC Devctl setup and usage
docs/playbooks/dmeta-system-playbook.md TTC Dmeta code generation workflow
web/README.md Pyxis pnpm workspace architecture, adding components, deployment
prototype-design/sql-api.md Pyxis SQL API design (backend API reference)
bot/discord/show-space/README.md Pyxis Discord bot integration
go-template/README.md go-template ASCII art title page (placeholder)
go-template/AGENT.md go-template Build commands, project structure, coding conventions
go-template/.github/workflows/push.yml go-template CI pipeline pattern
go-template/.github/workflows/release.yaml go-template Release pipeline pattern with split builds

Part VIII: Implementation Checklist for a New Project

Phase 1: Go Backend Scaffold

  1. Copy go-template to new directory
  2. Replace all XXX placeholders with project name
  3. Run go mod tidy && go generate ./... && go build ./...
  4. Verify make lint && make test pass
  5. Create the first Glazed verb (e.g., serve command)
  6. Add the embedded webapp server pattern (internal/webapp/)
  7. Add the //go:embed static directive and build-web script
  8. Create .devctl.yaml with dev profiles
  9. Push to GitHub and verify CI passes

Phase 2: React Frontend

  1. Create web/ directory with pnpm-workspace.yaml
  2. Create root package.json with workspace scripts
  3. Create component library package (atoms → molecules → organisms)
  4. Configure Storybook (.storybook/main.ts)
  5. Create src/styles/tokens.css with design tokens
  6. Create the application site package (pages, API, store)
  7. Configure Vite with proxy to Go backend
  8. Set up Redux store with makeStore() factory
  9. Set up RTK Query API slice with endpoints and tagTypes
  10. Run pnpm install && pnpm build && pnpm storybook

Phase 3: Integration

  1. Add backend/scripts/build-web.sh to build and embed frontend
  2. Add //go:generate bash ../../scripts/build-web.sh to main.go
  3. Configure SPA fallback route in Go server
  4. Test full-stack: devctl up → frontend connects to backend
  5. Add WebSocket route if needed
  6. Run visual regression tests with css-visual-diff

Phase 4: Dmeta Code Generation (optional, if design system needed)

  1. Create dmeta-ir/ directory with YAML metadata specifications
  2. Set up dmeta code generator (separate tool)
  3. Run generator to produce src/generated/dmeta-widgets/
  4. Configure componentManifest.ts and promotionState.ts
  5. Promote components from generated to hand-written as needed
  6. Add Storybook stories for generated and promoted components

Decision Records

Decision: pnpm workspace over Vite monorepo

  • Context: Need a monorepo structure for shared component library + app site.
  • Options considered: (1) pnpm workspaces, (2) Turborepo, (3) Nx, (4) Single Vite project with library mode.
  • Decision: pnpm workspaces with --filter targeting.
  • Rationale: Minimal configuration, native pnpm support, no additional build orchestration tool needed. Both existing projects use this pattern successfully.
  • Consequences: Must use pnpm --filter for all script execution. Workspace protocol dependencies (workspace:*) must be used for internal package references.
  • Status: accepted

Decision: Glazed verbs over raw Cobra commands

  • Context: Need structured command definitions with typed flags, composable settings, and output formatting.
  • Options considered: (1) Raw Cobra commands with manual flag parsing, (2) Glazed verb pattern, (3) Kong CLI framework.
  • Decision: Glazed verb pattern.
  • Rationale: All existing projects use Glazed. It provides declarative flag definitions, composable sections, automatic help generation, and structured output middleware. The Cobra bridge makes it compatible with the standard Go CLI ecosystem.
  • Consequences: New developers must learn the Glazed API (CommandDescription, fields.New, values.Values, sections). The dependency on glazed is significant.
  • Status: accepted

Decision: Redux Toolkit + RTK Query over React Query

  • Context: Need a data fetching and state management solution.
  • Options considered: (1) Redux Toolkit + RTK Query, (2) TanStack React Query + Zustand, (3) SWR + Context.
  • Decision: Redux Toolkit + RTK Query for Pyxis-style projects; Redux Toolkit (minimal) for TTC-style projects.
  • Rationale: RTK Query is integrated into Redux Toolkit, provides caching/invalidation/mutations out of the box, and the typed hook pattern works well. Pyxis uses this pattern successfully. TTC uses minimal Redux (empty reducer) because the chat architecture uses WebSocket-driven state rather than REST endpoints.
  • Consequences: RTK Query is optimal for REST APIs. For WebSocket-heavy apps, minimal Redux may suffice. Choose based on the data fetching pattern (REST vs WebSocket).
  • Status: accepted

Decision: net/http.ServeMux over third-party routers

  • Context: Need an HTTP router for the Go backend.
  • Options considered: (1) chi, (2) gin, (3) echo, (4) net/http.ServeMux (Go 1.22+).
  • Decision: net/http.ServeMux only.
  • Rationale: Go 1.22+ ServeMux supports method and path patterns (GET /api/v1/...). No third-party dependency needed. Consistent with AGENT.md guidelines.
  • Consequences: Must use Go 1.22+ pattern matching. Some advanced features (middleware chaining, route groups) require manual implementation.
  • Status: accepted

Risks and Open Questions

  1. go-template freshness: The go-template may drift from actual project patterns as conventions evolve. Needs periodic syncing.
  2. Dmeta documentation: The dmeta code generation pipeline is complex and not fully documented outside the TTC repo. A new project may not need it.
  3. Pyxis uses TanStack Query in the component library but RTK Query in the user site: This mixed approach can be confusing. The guideline recommends standardizing on RTK Query for new projects.
  4. Devctl plugin protocol: The .devctl.yaml plugin system uses NDJSON stdio protocol v2, which is documented in a separate skill. New projects need a Python or shell plugin.
  5. CI secrets management: The release workflow requires 6+ GitHub secrets. New projects must set these up manually.

References

Key Source Files

File Path Purpose
go-template scaffold ~/code/wesen/go-go-golems/go-template/ Go project scaffold
Glazed serve verb (Pyxis) ~/code/wesen/2026-04-23--pyxis/cmd/pyxis/cmds/serve.go Reference Glazed verb
Pyxis main.go ~/code/wesen/2026-04-23--pyxis/cmd/pyxis/main.go Cobra+Glazed+Help wiring
TTC main.go ~/code/wesen/2026-05-27--ttc-design-system/backend/cmd/ttc-garden-chat/main.go TTC Glazed verb
TTC store.ts ~/code/wesen/2026-05-27--ttc-design-system/web/packages/ttc-garden-assistant/src/store.ts Minimal Redux store
Pyxis store.ts ~/code/wesen/2026-04-23--pyxis/web/packages/pyxis-user-site/src/store.ts Redux + RTK Query store
Pyxis publicApi.ts ~/code/wesen/2026-04-23--pyxis/web/packages/pyxis-user-site/src/api/publicApi.ts RTK Query API definition
TTC GUIDELINES.md ~/code/wesen/2026-05-27--ttc-design-system/web/GUIDELINES.md Design system layer rules
TTC vite.config.ts ~/code/wesen/2026-05-27--ttc-design-system/web/packages/ttc-garden-assistant/vite.config.ts Vite + proxy config
go-template Makefile ~/code/wesen/go-go-golems/go-template/Makefile Standard Go Makefile
go-template .goreleaser.yaml ~/code/wesen/go-go-golems/go-template/.goreleaser.yaml Release configuration
go-template AGENT.md ~/code/wesen/go-go-golems/go-template/AGENT.md Go coding conventions
go-template lefthook.yml ~/code/wesen/go-go-golems/go-template/lefthook.yml Git hooks
TTC dmeta manifest ~/code/wesen/2026-05-27--ttc-design-system/web/packages/ttc-garden-assistant/src/dmeta/componentManifest.ts Dmeta component system
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment