Centinadocs

Components

Turn any box into a reusable component. The box you started from becomes the definition — it stays exactly where it is on the canvas, wearing the hollow brick mark — and every copy you place from the library is an instance, wearing the filled diamond cluster: a real subtree of ordinary boxes and text that remembers where it came from.

Centina's components are deliberately honest about what they are. An instance is not a locked proxy of the definition — restyle it, rewrite its text, move its pieces, and every edit records as a customization that survives updates. What an instance doesn't own is its structure: layers can't be added, removed, or reordered inside one — that shape is the definition's, and keeping it stable is what lets Centina tell you when an instance has drifted, replay your customizations after an update, and pull the definition's latest content back in when you ask.

Creating a component

Select a box and choose Create component from the right-click menu, or press ⌥⌘K. The box is promoted in place:

  • its label and selection outline turn the component purple, with the brick glyph (the hollow brick is the definition; instances wear the filled diamond cluster);
  • it enters the canvas's component library — the brick button at the top of the left rail, next to Present's play button. Click it and the library slides in over Folios and Layers; click again (or press Escape) and it slides away, your place in the layer tree untouched;
  • the component records its birth context: which page and folio it was born on, what contained it, when, and how big it was.

A small chip on the brick button carrying the purple sync glyph (a double-arrow cycle) means some instance is behind its definition — open the library for the per-component detail. The glyph means one thing everywhere it appears: behind — a sync is available here. The library is scoped to its canvas: components travel between canvases only when you copy/paste them (see "The provenance trail").

Only boxes become components (wrap anything else in a box first — ⌘B), and a box that's already a definition or an instance can't be reified again.

Placing instances

The library shows each component as a card: a search field on top, and under it thumbnails that are the definitions themselves — real DOM, scaled live, never a raster (a component whose definition lives on another page, or only as a snapshot, shows the hollow brick instead).

  • Drag a card out of the Components panel onto the canvas. The instance lands where you drop it — inside the box under your cursor, or on the canvas — and follows your pointer as a small chip while you drag. Escape cancels.
  • Right-click a card → Place instance drops one near what you're working on, never on top of existing work.
  • ⌘D on a definition stamps an instance next to it — duplicating a definition never creates a second definition.

Instances carry the filled diamond cluster. They're named after the component but the name is yours to change; renaming an instance never touches the library.

One rule is enforced: a component can't be placed inside its own definition (directly, or through another component that contains it). That drop is refused with a toast.

Editing, and staying in sync

Edit an instance's properties like anything else — move, resize, restyle, rewrite text. Edit the definition the same way, structure included. When an instance's content no longer matches its definition, Centina shows it:

  • the purple sync glyph on the library card (hover it for the count), on the instance's canvas label, and on its row in Layers (which is how a nested instance with no label of its own still shows it);
  • an Out of sync line in the inspector's Component card, with Bring into sync underneath it.

Position, size, rotation, name, visibility, and margin are the instance's own — changing those never counts as drift, and updating never touches them.

The instance card speaks Figma. At the top of the inspector, the component's name is the swap menu (click it), and the icon row beside it carries the three verbs: push changes to the definition (the diamond with the up arrow), reset all changes (the return arrow), and ⋯ — Go to definition (or Restore definition, when only a snapshot survives) and Detach instance. Under the name, From this canvas says where the component was born; an imported component names its source canvas instead.

Structure is sealed. Inside an instance you can't add, remove, or reorder layers, and the constraint anchors of interior layers can't change — that shape belongs to the definition. The editor simply doesn't offer it: drawing over an instance lands beside it, drops and pastes retarget to the nearest box outside it, the Layers rows don't drag, the menu items gray out, and the constraints row explains itself. Deleting or duplicating layers inside an instance shows a toast with the two ways out: edit the definition (instances follow when you bring them into sync), or detach (⌥⌘B) to turn the copy into plain boxes — and if you detach by accident, ⌘Z restores the link. The instance root itself stays an ordinary layer: move it, delete it, duplicate it, reorder it among its siblings.

Hiding a layer is the escape valve inside the seal: visibility is a property, so unchecking the eye on an instance's layer records as a customization — the closest thing to "removing" a piece of one copy.

Content edits on an instance become customizations. Change an instance's text, colors, or layout details and Centina records what you changed (the inspector reads "Customized — in sync"). Customizations are a fact, not a warning — they never raise the sync glyph, and they survive updates: when the definition moves on and you bring the instance into sync, the new content comes in and your customizations replay on top. A customization on a field hides definition changes to that same field — yours wins until you discard it.

The customization list. When an instance carries customizations, the sync line becomes a disclosure: open it and every property this instance sets differently from the definition is a row — which element, which property, your value — with a hover × to reset just that one. A grayed row with a dashed underline means the definition no longer has that property at all; the record does nothing and is pruned on the next update. The two whole-instance verbs live as the header icons, each direction spelled out in its tooltip:

  • Reset all changes — this copy goes back to the definition's values.
  • Push changes to definition — the definition adopts these values instead. A confirm spells out the consequence: this instance stops customizing them, and every other instance reads out of sync until you update it — pushing never silently changes the other copies. Pushing needs a live definition that isn't mid-face-edit, and an instance showing the base face; a customization inside a nested component is skipped with a note — open that component and edit it directly.

Bring into sync pulls the definition's current content into an instance: from the instance (inspector or right-click), from the definition (Update all instances), or from the library card. It replaces the instance's children and adopts the definition's layout behavior and styling, keeps the instance's geometry, name, selected face, and customizations, and is one undo step per page.

Tags — and states

Components carry freeform tags — "button", "marketing", "wip", whatever fits. Add them on the definition card (the tag chips with the little tag icon; type a name, Enter); they show on instances, on library cards, and they travel with the component through copy/paste.

Plain tags are deliberately inert in the editor — no behavior hangs off them. Their audience is agents: MCP surfaces tags on every component (get_canvas_info.components) and on any linked node (get_node_info), so an agent can judge what a component is before touching it. They're the first slice of the coming context feature.

A leading colon speaks to states instead. A state like :hover isn't a label — it's a variant face: the component's full content under that name. Type : in the tag input and the menu comes pre-populated with the standard interaction states — :hover, :pressed, :focus, the ones that compile to real CSS pseudo-classes — plus any states this component already has, filtered as you type. You never create :hover from scratch: pick it (Enter takes the highlighted row, arrows and click work too) and it's created and mounted for editing in one move; picking a state that already exists just re-enters it. A name of your own — :dark, :empty — still works: Enter creates it as a freeform variant (its face starts as a copy of the base). State chips carry the component purple (brick, colon, purple text) so they never read as plain tags — and because states are exclusive, at most one chip is ever filled: click a chip to edit that face on the canvas — it fills solid and the box label names the mounted face — click it again to save and return to the base. Double-click a chip to rename the face (every instance showing it follows along); hovering reveals × to delete it — deleting a face that instances are showing asks first, and those instances keep their look and read out of sync until updated. Chip tooltips whisper how many instances show each face.

Names compile to CSS by identity: hover / pressed / focus become real pseudo-classes, and a **name=value name groups into an axis** — size=sm and size=lg read as one family (the size= prefix mutes on the chips) and compile to [data-size="sm"] / [data-size="lg"] attributes on export. Anything else becomes a data-variant attribute.

Instances pick faces too. The same chips appear on an instance's card, live: click one and this copy shows that face — the chip fills, the canvas label wears the face's name, and your customizations stay on top. Click the filled chip to return to the base (there is no "base" chip — base is the absence of a selection, exactly as on the definition). One copy can be size=lg while another shows hover and a third stays on the base; sync is judged through each instance's selected face, so showing a face is never "out of sync." If a selected face is later deleted, nothing snaps: the chip renders dashed, the instance reads out of sync, and Bring into sync returns it to the base.

Swapping instances

Placed a Card where you want a Button? Click the component's name at the top of the instance's inspector card (the faint chevron beside it is the tell) and pick a replacement — the popover lists the library with live previews, grows a filter once the library is big enough, and keeps one promise: customizations that still apply carry over. The instance keeps its position, size, and (if you renamed it) its name; a selected face maps to the new component when it has one by the same name; and when some customizations can't carry (the new component has no matching element), a toast says exactly how many made it. Also available as right-click → Swap instance…, and from the library: hold ⌥ while dragging a row and drop it on an instance — the drag chip flips to "Swap to …" and the target outlines in purple so the aim is never ambiguous.

Where it's implemented

Agents can stamp implementation links onto a component over MCP (link_component_implementation): a label, URL, repo path, commit. They show under Implementation on the definition card (click to open, hover to remove) and in the event log — the design→code half of the provenance graph. An agent that scaffolds code from get_component records where it put it in the same breath.

Finding your way around

  • Go to definition (instance right-click, or ⋯ on the instance card) selects and frames the definition — and for as long as it stays selected, Escape returns you to the instance you came from (the definition's inspector card shows the same "Return to instance" chip). Click anywhere else and Escape means escape again.
  • Select instances (definition right-click or library card) selects every instance on the current page and frames them; when this page has none, it jumps to the busiest page that does. The toast counts anything beyond the frame.
  • Clicking a library card reveals the definition. Cards never mutate on click — restoring, placing, updating all live behind explicit verbs.

The provenance trail

Every component keeps its history — created, placed, detached, updated, renamed, imported, restored, each with where and when — but the inspector stays quiet about it by design: the log lives in the model and agents read it over MCP (get_component_info), alongside the immutable birth certificate (page, container, time, size).

Definitions travel with copy/paste. Paste a component (or anything containing instances of it) into another canvas and the definition comes along — same identity, origin intact, an "Imported" entry in its log. Paste when the target canvas already has that component and the copies simply link up to it. Cut/paste moves a definition, within a canvas or between canvases.

Deleting things

  • Deleting a definition (or its page) doesn't kill the component — the library keeps a snapshot, the card says "snapshot", and placing new instances keeps working. Restore definition (library card right-click, or ⋯ on any of its instances' cards) brings it back near your view; pasting the definition back revives it too.
  • Detach (⌥⌘B, inspector, or right-click) unlinks an instance — the boxes stay exactly as they are, only the link goes.
  • Delete component (library card, confirmed) removes it from the library: the definition becomes a plain box and every instance detaches. One undo step brings all of it back.

Interaction reference

ActionHowUndo/cancel
Open / close the librarythe brick button atop the left railEscape closes; display only, nothing to undo
Search the librarythe field atop the panel — matches names and tagsdisplay only; Escape clears the filter first
Create componentright-click → Create component, or ⌥⌘Kone undo step (sidecar + library row together)
Create components (bulk)select several boxes → ⌥⌘Kone undo step — one component per box
Place instance (drag)drag a library card onto the canvasEscape cancels the drag; drop is one undo step
Place instance (menu)card right-click → Place instanceone undo step
Stamp from definition⌘D on a definitionone undo step; the copy is an instance
Customize an instanceedit its content like anything elseone undo step; removes the edit AND its record together
Add/remove/reorder inside an instancesealed — draws & drops land beside the instance, menus gray out, ⌫/⌘D toast the ways outnothing happens, nothing to undo
Change interior constraint anchorssealed — the constraints row grays with the hintnothing happens; the instance root's own anchors stay editable
Hide a layer in one instancethe eye on its Layers rowone undo step; records as a customization (the seal's escape valve)
Bring into syncinstance inspector or right-clickone undo step; customizations replay on top
Update all instancesdefinition/row → Update N instancesone undo step per page touched
Reset one customizationopen the customization list → hover a row → ×one undo step; that property returns to the definition's value
Reset all changesthe return-arrow icon in the card headone undo step; back to the pure definition (a selected face stays)
Push changes to definitionthe push icon in the card head → confirmone undo step; siblings read out of sync — nothing auto-updates
Show a face on an instanceclick a face chip on the instance cardone undo step (Show hover face); click the filled chip for base
Swap an instanceclick the card's name · right-click → Swap instance… · ⌥-drag a library card onto itone undo step; carrying customizations replay
Add a tagdefinition card → tag chip +, type, Enterone undo step
Remove a taghover the chip → ×one undo step
Add a statetag input → :, pick a standard one (or type a custom name — size=lg makes an axis), Enterone undo step — state created and its face mounted
Rename a statedouble-click its chip on the definition cardone undo step; instance selections follow
Enter / switch a stateclick its chip, or type : and pick from the listone undo step; canvas swap and state table move together
Save & leave a stateclick the filled chipone undo step
Delete a statehover its chip → × (confirmed when instances show it)one undo step (a mounted face exits first; showing instances keep their look, read out of sync)
Rename componentrename the definition box, or card → Renameone undo step (box + library together)
Detach instance(s)⌥⌘B, the card's ⋯, or right-clickone undo step; nested instances keep their links
Delete definition box⌫ on the definitionone undo step; snapshot captured, undo restores the live definition
Delete componentcard right-click → Delete component (confirmed)one undo step; instances re-link, definition re-marks
Restore definitionsnapshot card right-click, or the instance card's ⋯one undo step (definition + snapshot-clear together)
Reveal definitionclick a library card, or the instance card's ⋯ → Go to definitioncamera move only — nothing to undo
Return to instanceEscape (or the inspector chip) after Go to definitioncamera move only; stale after any other selection
Select instancesdefinition right-click, or library cardselection + camera only — nothing to undo
Card thumbnailsalways live — the definition's real DOM, scaleddisplay only; the hollow brick when the DOM isn't reachable

Agents and the library

Agents hold the full surface, seal included: the structural tools (write_html, create_box, move_nodes, delete_nodes, duplicate_nodes) refuse below an instance root with an error naming the instance, the definition, and the ways out. The loop: create_component reifies a box (tags at birth), place_instances places linked copies (optionally born on a face), the ordinary tools do the customizing — edit an instance with update_styles or set_text_content and the change self-records as a customization — get_node_info reads the records, and update_instances carries the verbs: a bare {id} is Bring into sync, plus variant, swapTo, pushOverrides/resetOverrides (by the records' address+path), and detach. update_components patches the library side (rename, tags, faces — including mounting a face for editing: the agent edits the mounted definition with the ordinary tools and exits with mountState: null; nothing reads as drift mid-mount, but good etiquette is never leaving the human in the mode). get_component_info is the deep read: origin, the event log, faces with the CSS each compiles to, and every instance site with its sync status. link_component_implementation still stamps the design→code half of the graph.

Known limits (by design, for now)

  • Definition edits don't propagate automatically — updating is always an explicit, undoable act. The sync glyph is the nudge.
  • Customizations track property edits; structural edits are sealed rather than tracked (see above). Customizations inside a nested instance don't survive the outer instance's update — the outer content is replaced wholesale.
  • Plain tags carry no behavior by design — they're context for agents (and for you). States are the deliberate exception, and the leading colon is what marks a chip as one.
  • Editing a state's face is a mode: the base parks while the face is mounted, and everything that means "the component's content" (updates, sync glyphs, placement) keeps reading the base. The filled chip and the box label are the reminders; click the chip again to come back.
  • Pushing to the definition needs the instance on its base face — a face-selected copy would push base-shaped values at face content.
  • Deleting a page that holds a definition captures its snapshot outside the undo stream — page deletes themselves aren't undoable.