diff --git a/web/sections/AddComponentDialog.tsx b/web/sections/AddComponentDialog.tsx
index 5e21398..e5cb9cd 100644
--- a/web/sections/AddComponentDialog.tsx
+++ b/web/sections/AddComponentDialog.tsx
@@ -1,4 +1,4 @@
-import { Handshake, Image, Images, LayoutGrid, Mail, Type, type LucideIcon } from 'lucide-react';
+import { Handshake, Heading, Image, Images, LayoutGrid, Mail, Ruler, Type, type LucideIcon } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
/**
@@ -23,7 +23,30 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
* A kind the server would accept and this has never heard of is not creatable anyway.
*/
-export type ComponentKind = 'free-text' | 'media' | 'gallery' | 'contact-form' | 'partners' | 'collections';
+export type ComponentKind =
+ | 'free-text'
+ | 'media'
+ | 'gallery'
+ | 'contact-form'
+ | 'partners'
+ | 'collections'
+ | 'collection-title'
+ | 'collection-description'
+ | 'collection-specs'
+ | 'collection-gallery';
+
+/*
+ The blocks that draw whatever the page is ABOUT.
+
+ Only offered on a template. On an ordinary page they would render nothing, which is the worst kind of
+ block: one that is available, adds cleanly, and silently does nothing.
+*/
+export const SUBJECT_KINDS: ComponentKind[] = [
+ 'collection-title',
+ 'collection-description',
+ 'collection-specs',
+ 'collection-gallery',
+];
type Choice = {
kind: ComponentKind | null;
@@ -66,16 +89,36 @@ export const CHOICES: Choice[] = [
{ kind: 'contact-form', label: 'Contact form', hint: 'The site\'s form, as it is', Icon: Mail },
{ kind: 'partners', label: 'Partners', hint: 'The partner logos, from Site', Icon: Handshake },
{ kind: 'collections', label: 'Collections', hint: 'Every collection, as a grid', Icon: LayoutGrid },
+
+ /*
+ The four that draw THIS collection, on the template.
+
+ They hold nothing — not even a reference. The subject is the URL, so one block draws a different
+ collection on every request, and that is what lets one arrangement serve all of them.
+ */
+ { kind: 'collection-title', label: 'Its name', hint: 'The collection this page is about', Icon: Heading },
+ { kind: 'collection-description', label: 'Its description', hint: 'What was written about it', Icon: Type },
+ { kind: 'collection-specs', label: 'Its specs', hint: 'Measurement, material, colours, delivery', Icon: Ruler },
+ { kind: 'collection-gallery', label: 'Its photographs', hint: 'The gallery it points at', Icon: Images },
];
export const AddComponentDialog = ({
open,
onCancel,
onChoose,
+ template,
}: {
open: boolean;
onCancel: () => void;
onChoose: (kind: ComponentKind) => void;
+ /**
+ * Whether this page is a TEMPLATE, which decides whether the subject blocks are offered.
+ *
+ * Hidden rather than disabled elsewhere. A greyed-out row invites the question "why not here", and
+ * the answer — this page has no subject — is not something a tooltip fixes. On a page that has one
+ * they are the point; on any other they would render nothing at all.
+ */
+ template?: boolean;
}) => (
diff --git a/web/sections/PageBuilderPanel.tsx b/web/sections/PageBuilderPanel.tsx
index 6d556d5..11c5faf 100644
--- a/web/sections/PageBuilderPanel.tsx
+++ b/web/sections/PageBuilderPanel.tsx
@@ -1011,7 +1011,13 @@ export const PageBuilderPanel = () => {
onCreate={(values) => void createSection(values)}
/>
- setChoosing(null)} onChoose={chooseKind} />
+ setChoosing(null)}
+ onChoose={chooseKind}
+ // Which decides whether the subject blocks are offered — they render nothing anywhere else.
+ template={page?.is_template}
+ />
` is drawn through it. Listed beside the
+ * pages because it is built exactly like one — a separate section for a single row would be a second
+ * place to learn — and marked so nothing offers to rename it, reorder it or put it in a menu.
+ */
+ is_template?: boolean;
};
/**