Bluefly Drupal Site-Building Doctrine¶
Status: BINDING. Scope: every Bluefly Drupal site, Site Template, Recipe, theme, frontend library, Canvas implementation, Drupal coding agent.
Defines HOW agents build Drupal sites — not a suggestion. Companion to Drupal AI Best Practices (which owns the Drupal AI/orchestration/agent ladder); this doctrine owns page composition, components, themes, and the frontend build chain. Every Drupal agent's base context is both documents together — a site-specific context describes only what differs about that site, never repeats either doctrine.
Goal: Canvas-native, component-driven, contrib-first, package-managed, reusable, accessible, upstream-compatible, source-controlled, reproducible, portable, maintainable without the agent that created it. Agents build a Drupal product, not a pile of HTML/CSS/JS/Twig-overrides/copied-assets that happens to look right.
1. The fundamental model¶
DRUPAL owns content/entities/fields/permissions/APIs/config/workflows/routing/rendering/app behavior. CANVAS owns page composition and editorial assembly. COMPONENT LIBRARY owns reusable visual/product components and design primitives. THEME owns Drupal-specific presentation that must exist outside Canvas. STUDIO-UI is Bluefly's preferred reusable UI source/design-system dependency. NPM owns JS/frontend package deps. COMPOSER owns Drupal/PHP package deps. RECIPES own reusable Drupal config/feature composition. SITE REPOSITORY owns only what's genuinely site-specific. These boundaries must not collapse for convenience.
2. Default build order¶
- Drupal Core
- Drupal CMS
- Maintained contrib
- Canvas capability
- Canvas ecosystem project
- Studio UI component
- Shared component-library component
- Configuration
- Recipe
- Canvas composition
- Existing SDC
- Existing Canvas Code Component
- Thin extension of existing component
- New reusable component
- Theme implementation where Drupal rendering actually requires it
- Custom Drupal code last
Never ask "where can I put code that makes this work" — ask "what capability already owns this" and "where should this live so customer #2 can reuse it." This is the frontend/composition-layer counterpart to drupal-standard.md §6/§12's backend/AI ladder.
3. Canvas is the default page-composition system¶
If Canvas is enabled, assume it's the default composition mechanism unless proven otherwise. Don't build landing pages via page--front.html.twig, page--landing.html.twig, node--123.html.twig, large preprocess functions, hardcoded HTML templates, custom page-builder modules — when the requirement is page composition, use Canvas. Canvas owns: page composition, component placement/configuration, slots, reusable page structures, content templates/global regions where supported, editor-controlled presentation. Don't recreate Canvas in Twig.
Canvas MUST NOT own the canonical copy of: service or product descriptions, work proof, person bios, capability descriptions, repeated evidence or metrics, SEO metadata, organisation facts. Those are entity fields bound into Canvas. A Canvas page may own page-specific narrative, section order, selected featured content, presentation variants and CTA placement — and nothing that a second page would need to repeat.
4. Content is not component source¶
Separate: CONTENT (entities/fields), COMPOSITION (Canvas config describing assembly), COMPONENT (reusable presentation/functionality), THEME (Drupal structural rendering outside Canvas), DESIGN SYSTEM (reusable visual language/primitives). Don't encode editorial content into component source (bad: Hero.jsx contains the homepage headline; good: Hero exposes title/description/image/actions/variant/alignment as props, Canvas/content supplies values).
4a. Editorial form architecture¶
The editing experience is part of the product. Every reusable business bundle SHOULD present the same form architecture, implemented with form displays and field groups (Drupal CMS form-display capabilities where available), in this order: Core (title, short title, summary, status); Story (the main editorial fields — problem, approach, outcome); Relationships (references to the other business bundles and topics); Media (hero, supporting, gallery); Discovery / SEO (alias, meta title, meta description, social image); Promotion (featured, CTA label and destination, display priority); Governance (publication state, review date, owner, evidence or source references). Field labels are plain English; help text explains what a field is for, not how Drupal stores it. Alt text is required for meaningful images; AI-assisted alt text may propose it, editorial review remains required.
5. Components must have real contracts¶
PROPS = configurable data/behavior, SLOTS = composable child content. Don't create a new component because copy changed — don't create bluefly-homepage-hero/customer-a-hero/customer-b-hero when these are instances of hero. Model reusable visual concepts: hero, card, card-grid, section, container, heading, callout, testimonial, stat, feature, logo-cloud, accordion, tabs, button-group, media, nav element. Site-specific composition belongs in Canvas.
6. Component metadata is part of the API¶
Props/slots are the public interface, not optional docs. Define name/description/group/status/props/required-props/slots/examples/validation per current Canvas/SDC tooling schemas. Don't invent undocumented parameters or infer schemas when Canvas tooling can determine them.
7. Choose SDC vs Canvas Code Component intentionally¶
Not by agent language preference. Use SDC when the capability belongs to Drupal/theme rendering and benefits from Twig, render arrays, Drupal libraries, server-side rendering, theme ownership, Drupal-native presentation. Use Canvas Code Components when it benefits from Canvas-native composition, React/Preact, npm packages, interactive browser behavior, shared JS/TS, local component dev, Canvas CLI sync. Both valid — neither excuses duplicating the other.
8. Studio UI is the Bluefly UI source to check first¶
Canonical: gitlab.com/blueflyio/agent-platform/tools/studio-ui. Before creating a reusable visual primitive, inspect Studio UI for an existing component/primitive/layout/pattern/token/interaction/utility.
Sequence: STUDIO_UI_EXISTING → use; STUDIO_UI_NEEDS_GENERALIZATION → generalize in Studio UI; GENERIC_COMPONENT_MISSING → add to Studio UI or designated shared library; SITE_SPECIFIC_REQUIREMENT → keep in site/theme/Canvas.
Never copy Studio UI source into the site, fork a component just to change styling, or paste generated output into Twig — consume through the supported package/build interface. If Studio UI lacks a clean interface for a Drupal/Canvas use case, fix the interface at the owner, don't solve architecture by copying files.
9. NPM packages are dependencies — use them¶
Canvas Code Components support npm packages. Prefer existing package → package config → thin adapter → custom implementation last.
Never: download JS from a website, copy node_modules files, paste library source into components, copy package dist files into the theme, commit node_modules, vendor npm libraries manually, use CDN script tags as a package-management workaround, copy an npm package into Drupal libraries/, rewrite a library because setup seems hard.
If it's an npm dependency, declare it as one — metadata/lockfiles make the build reproducible.
10. Use Canvas's built-in packages first¶
Current Canvas ships built-in packages (Tailwind, clsx, class-variance-authority, Drupal JSON:API clients, SWR, tailwind-merge, drupal-canvas utilities — list changes, don't freeze it as eternal truth, check current docs). If Canvas already provides it, use the supported upstream package rather than introducing a duplicate.
11. Third-party npm packages¶
Allowed if browser-compatible, actively maintained, appropriately licensed, reasonably sized, necessary, declared normally, locked reproducibly. Install/import normally, let Canvas's supported build/push package it. Don't invent a separate bundler (Webpack/Vite/esbuild) just because the agent knows one — use Canvas's supported toolchain unless a gap is proven.
12. No outside files (absolute)¶
Implementation must be reconstructable from declared source repos and package managers. Never depend on ~/Downloads, ~/Desktop, /tmp, random Mac files, another checkout, another customer's project, agent scratch dirs, untracked generated files, manually copied assets, NAS files without package ownership, files from another DDEV project.
Every required file needs a legitimate owner: site repo, theme repo, component-library repo, Studio UI, Drupal module, Composer package, npm package, managed Drupal content/media, approved artifact/package registry. "No owner" = incomplete implementation.
13. No copy-in development¶
Never cp/rsync another project's source in, copy-and-edit a contrib module, copy Studio UI files into Drupal, copy Canvas generated output manually, copy npm package files into the repo. Reusable code gets packaged; code belonging elsewhere gets its owner changed; dependencies get declared; composition goes through Canvas.
14. No installed-copy hacks¶
Never edit Composer-installed source inside a consumer — web/modules/contrib, web/themes/contrib, vendor, and any Composer-owned Bluefly package under web/modules/custom, web/themes/custom, recipes. Writable ≠ site-owned. If Composer can replace the file, fix the owning repository instead. Same rule as drupal-standard.md's "generated trees are never source."
15. Component library ownership¶
Owns things reusable across multiple Drupal products/sites: design tokens, generic visual/interactive primitives, common layouts/typography, buttons/cards/alerts/nav primitives, form presentation primitives, icons, generic a11y behavior, shared JS/TS utilities+tests, shared Storybook/workbench fixtures, framework-neutral or Canvas-compatible UI primitives.
Must NOT contain: customer content, site nav structure, site-specific compositions/config/entity assumptions, unparameterizable customer branding, unrelated Drupal templates.
Test: "could customer #2 use this without copying it?" Yes → library. No → site/theme/Canvas.
16. Theme repository owns Drupal presentation¶
Not a component junk drawer — owns presentation specifically belonging to Drupal's rendering layer: html.html.twig, page.html.twig, regions, Drupal chrome/nav integration, system messages, forms where Drupal rendering requires them, Views presentation Canvas doesn't own, field/entity presentation outside Canvas, Drupal-specific SDCs, theme libraries/settings, Drupal-specific a11y integration, Drupal-to-design-system adaptation, global Drupal shell behavior.
Theme consumes shared UI packages, doesn't duplicate the shared component library.
SHARED LIBRARY=reusable visual capability, THEME=Drupal adapter+structural presentation, CANVAS=page composition, SITE=product/content/config.
A theme owns presentation only. Controllers, services, theme negotiators, performance monitors, protocol bridges and any other PHP that is not a preprocess, a theme hook or an SDC belong in a module (and, by the ladder, probably in an upstream one) — never in a theme.
17. Site repository ownership¶
Stays thin: composer.json/lock, site config, Recipe composition, site-specific config, Canvas site composition where exported/synced, content-model config, deployment config, site-specific theme dependency/config, site-specific tests/acceptance criteria.
Should NOT become the permanent home for reusable modules/themes/component systems/JS libraries/design systems/generic agent skills — those need their own owner. Same site-context-stays-thin principle as drupal-standard.md §18, applied to code rather than docs.
18. Assets¶
EDITORIAL→Drupal Media/content. COMPONENT→component owner. SHARED DESIGN→Studio UI/shared library. THEME STRUCTURAL→theme. NPM-PROVIDED→npm dependency/build.
Never create misc/, stuff/, assets-old/, copied/, temp/, generated-final/, final-final/ as architectural layers.
19. Canvas local codebase is real source¶
When Code Components need source control, npm deps, shared code, static assets, SVGs, metadata, local testing — use Canvas's supported local-codebase workflow via the current official Canvas CLI. Don't treat the browser editor as the only authoring environment for substantial components.
Lifecycle: source → local validation → Canvas CLI → Canvas → behavioral verification. Use current upstream commands/docs, not memorized syntax.
20. Canvas push/pull is not a substitute for ownership¶
push/pull synchronize artifacts, they don't answer "which repository owns this component." Owning repo must stay explicit — don't let repeated pull/edit/push cycles across unrelated repos create competing authorities.
Before sync:
SOURCE_OWNER=
TARGET_SITE=
DIRECTION=
ARTIFACT_TYPE=
21. DDEV worktree law¶
Canonical worktree location inside DDEV: /var/www/worktrees. Never in /tmp, /var/tmp, /home/*, web/, vendor/, another package's installed directory.
Producer-package work from inside DDEV: identify owning repo → claim work → create/use producer worktree under /var/www/worktrees → modify producer source → validate → commit → push/MR through governed workflow → update consumer dependency → verify consumer. The consumer's installed package is never the worktree.
22. Outside-DDEV worktree law¶
Don't invent ad-hoc worktree management — use Gas City/Beads for work ownership, rig/project scope, worktree lifecycle, dispatch, continuation. Use current supported gc commands, check current help/docs rather than hardcoding remembered syntax.
Flow: Gas City → claim/dispatch Bead → owning rig/repo → managed worktree → implement → test → MR → release branch → continuation. No manual ad-hoc worktrees outside governed Gas City flow.
23. Detect execution context first¶
Before source mutation, determine:
EXECUTION_CONTEXT=<DDEV|HOST>
SITE_REPO=
DRUPAL_ROOT=
SOURCE_OWNER=
TARGET_PACKAGE=
WORKTREE_ROOT=
BEAD=
BRANCH=
DDEV → WORKTREE_ROOT=/var/www/worktrees. HOST → WORKTREE_MANAGER=GAS_CITY. If undetermined: stop before mutation.
24. Producer/consumer law¶
Every package interaction has a producer and consumer (Studio UI→theme, theme→site, contrib module→site). Never fix a producer defect in the consumer. Never make the consumer carry a fork because opening the producer repo takes longer.
25. Contrib first¶
Before writing Drupal PHP: search Core, Drupal CMS, contrib, read current docs, check maturity/security coverage, check installed/existing Bluefly packages.
Classify: CORE_SOLVES / CONTRIB_SOLVES / CONFIG_SOLVES / RECIPE_SOLVES / CANVAS_SOLVES / SDC_SOLVES / ECA_SOLVES / TOOL_API_SOLVES / EXISTING_BLUEFLY_SOLVES / PROVEN_GAP. No PROVEN_GAP = no new custom module.
26. Extend, don't fork¶
If contrib gets 90% there: use contrib + extension point/plugin/event/config/upstream patch. Don't copy the module into bluefly_better_whatever or maintain a permanent fork for the remaining 10%. Contribute generally useful fixes upstream.
27. CSS law¶
Don't solve every visual discrepancy by appending CSS. Determine ownership first: design token? component? variant? Canvas config? theme? site-specific exception? Prefer tokens and component variants.
Avoid !important escalation, selector wars, DOM-position selectors, generated Canvas selectors, page/node-ID selectors, one-off inline styles, duplicated utility CSS. Don't let CSS compensate for wrong component architecture.
28. JavaScript law¶
Must have an owner and reason. Prefer Canvas Code Component behavior, shared component-library behavior, Drupal behaviors where Drupal integration requires them, existing npm package.
Don't add global site JS for behavior belonging to one component. Don't manipulate Canvas-generated DOM externally or build fragile DOM observers around Drupal/Canvas markup.
29. Accessibility is component acceptance, not cleanup¶
Reusable components must consider: semantic HTML, keyboard operation, focus behavior, labels, accessible names, ARIA only where needed, contrast, reduced motion, responsive behavior, zoom, screen-reader behavior. Looking correct while violating the a11y contract = not complete.
30. Responsive design is component behavior¶
Don't build separate desktop/mobile component copies without a real semantic distinction. Define responsive behavior intentionally in the component. Canvas composition shouldn't have duplicate mobile/desktop sections hidden via CSS.
31. Design implementation flow¶
Not: screenshot → eyeball → giant HTML blob → giant CSS blob. Instead: design → identify tokens → identify reusable primitives → inspect Studio UI → map existing components → identify missing reusable components → implement/generalize at correct owner → expose props/slots → compose in Canvas → verify visually and behaviorally. The page is the result of components, not the component source.
32. Never create a parallel design system¶
Studio UI exists. Before introducing colors/spacing/typography scales, buttons, cards, form styles, icons, breakpoints, motion rules, component primitives — inspect Studio UI. Missing generally-useful capability → improve Studio UI. Don't create another design system inside a Drupal theme — the theme adapts the shared system to Drupal.
33. No build-system invention¶
Don't add Webpack/Rollup/Vite/esbuild/Gulp/Grunt/custom shell bundlers just because the agent wants a build process. Use the build system Canvas/Studio UI/the owning npm package/the existing theme already provides. A new build layer needs a demonstrated gap.
34. No CDN hacks¶
Don't solve dependency problems with <script src>, <link href>, remote runtime npm loaders, unpkg, jsdelivr — unless the product explicitly requires that external service. Package dependencies properly; production shouldn't depend on a random public CDN because an agent couldn't make the package build work.
35. No generated-source confusion¶
Generated output isn't automatically source. Determine: SOURCE / GENERATED_ARTIFACT / RUNTIME_STATE / CONTENT / CONFIGURATION. Don't edit generated artifacts to fix source. Don't commit build output unless the owning package explicitly requires it.
36. Test at the right layer¶
COMPONENT: tests/workbench/render/a11y. THEME: Drupal rendering+integration. CANVAS: component availability+props/slots+composition. SITE: actual page behavior. DRUPAL: config/content/access/workflow behavior. A component isn't complete because its source compiled.
37. Outcome verification¶
npm install PASS ≠ component works. composer require PASS ≠ Drupal integration works. canvas push PASS ≠ Canvas renders correctly. config import PASS ≠ site works. component appears PASS ≠ props work. page renders PASS ≠ accessibility passes. CI green PASS ≠ acceptance criteria satisfied. Verify the actual outcome. Same discipline as drupal-standard.md §17.
38. Required pre-build report¶
Before creating a new component/frontend implementation:
REQUIREMENT=
EXECUTION_CONTEXT=
SOURCE_OWNER=
CORE_CAPABILITY=
CONTRIB_CAPABILITY=
CANVAS_CAPABILITY=
STUDIO_UI_MATCH=
SHARED_LIBRARY_MATCH=
EXISTING_SDC_MATCH=
EXISTING_CODE_COMPONENT_MATCH=
NPM_DEPENDENCY_NEEDED=
EXISTING_NPM_PACKAGE=
TARGET_LAYER=
TARGET_REPOSITORY=
WORKTREE=
WHY_NEW_CODE_IS_REQUIRED=
Empty WHY_NEW_CODE_IS_REQUIRED = do not create new code.
39. Required component placement decision¶
For every new reusable frontend artifact:
IS_GENERIC_UI=
IS_DRUPAL_SPECIFIC=
IS_SITE_SPECIFIC=
IS_EDITORIAL_COMPOSITION=
GENERIC_UI → Studio UI/shared library. DRUPAL_SPECIFIC_PRESENTATION → theme. SITE_SPECIFIC_COMPOSITION → Canvas/site config. EDITORIAL_CONTENT → Drupal content/media. GENERIC_DRUPAL_CAPABILITY → contrib/upstream module or appropriate reusable package. Don't choose a repo for convenience.
40. Final acceptance¶
Before declaring frontend work complete, all must be YES: UPSTREAM_REVIEWED, CONTRIB_REVIEWED, CANVAS_USED_WHERE_APPROPRIATE, STUDIO_UI_REUSED_WHERE_APPROPRIATE, NO_INSTALLED_PACKAGE_EDIT, NO_OUTSIDE_FILES, NO_COPY_IN_CODE, NO_CDN_HACK, NO_NODE_MODULES_COMMITTED, NO_PARALLEL_DESIGN_SYSTEM, NO_UNDECLARED_DEPENDENCIES, COMPONENT_OWNER_CLEAR, THEME_OWNER_CLEAR, CANVAS_COMPOSITION_CLEAR, NPM_REPRODUCIBLE, COMPOSER_REPRODUCIBLE, ACCESSIBILITY_VERIFIED, RESPONSIVE_VERIFIED, CANVAS_BEHAVIOR_VERIFIED, DRUPAL_BEHAVIOR_VERIFIED, SOURCE_CAN_BE_REBUILT_FROM_GIT_AND_PACKAGE_MANAGERS. Any NO = not done.
41. Final law¶
Build Drupal with Drupal. Compose with Canvas. Reuse contrib before custom PHP. Reuse Studio UI before inventing UI. Use npm/Composer as package managers, not copy/paste. Keep generic UI in its library, Drupal structure in its theme, page composition in Canvas, site-specific state in the site. Inside DDEV: /var/www/worktrees. Outside DDEV: Gas City manages worktrees. Fix the owner, never the installed copy. No outside files. No hacks. No parallel design systems. No new code until the gap is proven.
Ownership boundary table¶
| Concern | Studio UI / shared library | Drupal theme | Canvas/site |
|---|---|---|---|
| Design tokens | Yes | Consume/adapt | Consume |
| Generic button/card/hero primitives | Yes | Consume | Compose |
| Generic JS/TS utilities | Yes | Consume if needed | — |
| Generic npm-backed interaction | Yes | Adapter only | Compose |
Drupal html.html.twig / page.html.twig |
No | Yes | No |
| Drupal regions/messages/forms | No | Yes | No |
| Drupal-specific SDC | Usually no | Yes | Use |
| Customer homepage arrangement | No | No | Canvas |
| Customer copy/images | No | No | Drupal content/media |
| Page-specific layout | No | Usually no | Canvas |
| Generic accessibility behavior | Yes | Drupal adaptation | Consume |
| Drupal render-array integration | No | Yes | No |
| Reusable component visual variants | Yes | Consume | Select/configure |
| Customer-specific theme setting | No | Yes | Configure |