Skip to content

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

  1. Drupal Core
  2. Drupal CMS
  3. Maintained contrib
  4. Canvas capability
  5. Canvas ecosystem project
  6. Studio UI component
  7. Shared component-library component
  8. Configuration
  9. Recipe
  10. Canvas composition
  11. Existing SDC
  12. Existing Canvas Code Component
  13. Thin extension of existing component
  14. New reusable component
  15. Theme implementation where Drupal rendering actually requires it
  16. 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.

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).

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.

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