-
Notifications
You must be signed in to change notification settings - Fork 27
Night Watch Figma Librarian
Assigned to: Ernest's Navi (ernestt)
Goal: Keep the Astryx OSS Figma Library in sync with the code in packages/core/src/.
Schedule: Once per day at 12pm PST, Mon–Fri. Requires Ernest's Mac with Figma open and Claude Code connected via MCP. If the laptop is off (PTO, etc.), the run skips and catches up on the next available day — drift accumulates harmlessly.
GChat notification space: https://chat.google.com/room/AAQAAo12MnA?cls=7
The Astryx Figma library and the Astryx code library are two representations of the same design system. When a component gets a new prop, variant, or size in code, the Figma library should reflect that. Today this sync is manual and drifts silently — a designer builds with outdated Figma components, ships a handoff, and the engineer discovers the mismatch during implementation.
The Figma Librarian closes this loop automatically.
- Read new unresolved comments on the Figma library file since last run (via REST API)
- Detect code changes to component prop interfaces since last run
- Classify each change (new prop, removed prop, renamed prop, new variant value, new component)
- Read the current Figma component state via
use_figmaMCP tool - For existing components with prop changes: modify the Figma component in-place (add/remove/rename variant axes, boolean toggles, text properties, instance swap slots)
- For brand new components: run the full CC Figma Builder 4-phase workflow (Research → Check Dependencies → Plan Variants → Build → Verify)
- Respect human overrides — if a designer manually adjusted a Figma component, preserve their changes unless there's a direct conflict with code
- Log every action taken with before/after state
- Send a summary to GChat after each run
- Visual design judgment (that's the Designer role)
- Code review or PR review (that's Reviewer)
- CI fixes (that's QA)
- Issue triage (that's PM)
- Component code auditing (that's Component Auditor)
- Publish the Figma library — humans publish
┌──────────────────────────────────────────────────────────────┐
│ STEP 0: READ FIGMA COMMENTS │
│ │
│ GET /v1/files/c9CR0F4jJQwhue60Ec7qOz/comments?as_md=true │
│ (Figma REST API — works from sandbox, no MCP needed) │
│ │
│ → Filter to unresolved comments created since last run │
│ → Classify each comment: │
│ │
│ QUESTION ("Does Button have xl?") │
│ → Answer from code via Figma reply │
│ │
│ REQUEST with no code match ("Add compact density") │
│ → Do NOT action. Surface in GChat for human review │
│ → Code is king — Figma reflects code, not vice versa │
│ │
│ PARITY ("Code has ghost variant, Figma doesn't") │
│ → Verify against code. If confirmed, queue as a │
│ sync action in STEP 4. Reply in Figma after fixing. │
│ │
│ → Track seen comment IDs in state to avoid duplicates │
│ → Never resolve or delete comments — humans do that │
└───────────────────────────┬──────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ STEP 1: DETECT CODE CHANGES │
│ │
│ git log --since="{lastRunAt}" --name-only -- packages/core/ │
│ → list files changed under packages/core/src/{Component}/ │
│ → for each changed component, read Astryx{Component}.tsx │
│ → extract the props interface: │
│ • variant union types (e.g. ButtonVariant) │
│ • size union types (e.g. ButtonSize) │
│ • boolean props (isDisabled, isLoading, isIconOnly) │
│ • string/text props (label, tooltip, placeholder) │
│ • ReactNode/slot props (icon, children, endContent) │
│ • event handlers (skip — no Figma representation) │
│ • HTML passthrough props (skip — infrastructure) │
└───────────────────────────┬──────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ STEP 2: CLASSIFY CHANGES │
│ │
│ Compare extracted props against last-known state: │
│ │
│ NEW_COMPONENT — component dir exists in code, not in Figma │
│ PROP_ADDED — new variant value, boolean, text, or slot │
│ PROP_REMOVED — variant value or prop deleted from code │
│ PROP_RENAMED — prop name changed (heuristic: add+remove │
│ in same commit with similar type) │
│ VALUES_CHANGED — union type expanded/contracted │
│ NO_FIGMA_IMPACT — only event handlers, internal refactor, │
│ style-only changes, test changes │
│ │
│ Skip components that are infrastructure-only: │
│ Layer, SizeContext, Field (internal), Center, Stack, Layout, │
│ Grid, AspectRatio — these have no meaningful Figma component │
└───────────────────────────┬──────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ STEP 3: READ FIGMA STATE (via use_figma MCP) │
│ │
│ For each component that needs attention: │
│ │
│ a) Find the component set: │
│ figma.root.findOne(n => │
│ n.type === "COMPONENT_SET" && n.name === "Astryx{Name}") │
│ │
│ b) Read current properties: │
│ cs.componentPropertyDefinitions │
│ cs.children.map(c => c.name) // variant names │
│ │
│ c) Read last-known agent state from state file │
│ │
│ d) Compute three-way diff: │
│ CODE state (current) │
│ FIGMA state (current, from MCP) │
│ AGENT state (last time agent touched it) │
│ │
│ This three-way diff enables human override detection: │
│ If FIGMA ≠ AGENT but CODE hasn't changed → human edited it │
│ If FIGMA ≠ CODE and FIGMA = AGENT → code changed, update │
│ If FIGMA ≠ CODE and FIGMA ≠ AGENT → conflict, see rules │
└───────────────────────────┬──────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ STEP 4: APPLY CHANGES │
│ │
│ For EXISTING components (update in-place): │
│ │
│ PROP_ADDED / VALUES_CHANGED: │
│ → use_figma: add new variant to component set │
│ → use_figma: add new boolean/text/swap property │
│ → use_figma: get_screenshot → verify │
│ │
│ PROP_REMOVED: │
│ → use_figma: remove variant from component set │
│ → use_figma: remove property definition │
│ → use_figma: get_screenshot → verify │
│ │
│ PROP_RENAMED: │
│ → use_figma: rename property/variant axis │
│ → use_figma: get_screenshot → verify │
│ │
│ For NEW components (full build): │
│ → Run CC Figma Builder 4-phase workflow: │
│ Phase 1: Research code source (read component file) │
│ Phase 1.5: Check sub-component dependencies exist │
│ Phase 2: Plan variant matrix │
│ Phase 3: Build in Figma via use_figma (incremental) │
│ Phase 4: Screenshot verification (MANDATORY) │
│ │
│ IMPORTANT: Work incrementally. One use_figma call per │
│ logical step. Validate after each step. Never try to build │
│ an entire component in one script. │
└───────────────────────────┬──────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ STEP 5: RECORD & REPORT │
│ │
│ a) Update state file with new AGENT state for each component │
│ b) Log all actions to memory/xds-night-watch/{date}.md │
│ c) Send GChat summary to notification space │
│ d) If any conflicts were detected, include them in summary │
│ with recommendation for human review │
└──────────────────────────────────────────────────────────────┘
The Figma library is a shared artifact. Designers may manually adjust Figma components for visual reasons that don't map cleanly to code props. The agent MUST respect this.
The agent maintains three states per component:
| State | Source | Purpose |
|---|---|---|
| CODE | Parsed from Astryx{Component}.tsx props interface |
What the code says the component should be |
| FIGMA | Read from Figma via use_figma MCP at runtime |
What the Figma component actually looks like right now |
| AGENT | Last state the agent wrote to Figma (from state file) | What the agent last set it to |
| CODE changed? | FIGMA = AGENT? | Action |
|---|---|---|
| Yes | Yes | Auto-update. Code changed, Figma matches what agent last set. Safe to update. |
| Yes | No | Conflict. Code changed AND someone manually edited Figma since agent's last run. See conflict resolution below. |
| No | Yes | No action. Nothing changed. |
| No | No | Human override. Code didn't change but Figma was manually edited. Preserve the human's changes — update AGENT state to match current FIGMA (so we don't flag this next run). |
When both code and a human have changed the same component:
-
Additive code changes (new variant value, new prop): Apply the code change alongside the human's edits. Adding a new
size="xl"variant doesn't conflict with a designer adjusting padding on thesize="md"variant. -
Direct conflicts (code removed a prop that the human modified, or code renamed something the human customized): Apply the code change — the code is the source of truth for what props exist. But log the conflict prominently in the run report and flag it in GChat so a human can review.
-
Ambiguous conflicts (human changed a property value that code also changed — e.g., both changed default variant spacing): Do not auto-apply. Log the conflict and skip. Let a human decide.
Any change to a Figma component property or variant that wasn't made by the agent. Detected by comparing current FIGMA state against stored AGENT state. If they differ and the agent didn't run since the change, a human (or another tool) made the edit.
How TypeScript props map to Figma component concepts:
| Code Pattern | Figma Representation | Example |
|---|---|---|
Union type prop (variant?: 'primary' | 'secondary') |
Variant axis | variant = primary, variant = secondary |
Size union (size?: 'sm' | 'md' | 'lg') |
Variant axis | size = sm, size = md, size = lg |
Boolean prop (isDisabled?: boolean) |
Boolean component property | Toggle in properties panel |
String prop (label: string) |
Text component property | Editable text in properties panel |
Single fixed-type ReactNode prop (icon?: ReactNode) |
Instance swap property (INSTANCE_SWAP) |
User picks from compatible components |
Freeform ReactNode prop (children?: ReactNode) |
Figma Slot (SLOT property + SlotNode) |
Freeform content area — users add/remove/rearrange layers without detaching |
Repeating ReactNode prop (items?: ReactNode[]) |
Figma Slot (SLOT property + SlotNode) |
Slot with repeating instances inside |
Mixed content area (endContent?: ReactNode) |
Figma Slot or Instance swap | Slot if content varies freely; instance swap if it's always a single component type |
| Code Pattern | Why no Figma mapping |
|---|---|
Event handlers (onClick, onChange) |
Behavior, not visual |
Ref (ref?: Ref<HTMLElement>) |
Infrastructure |
HTML attributes (className, style, id) |
Infrastructure |
xstyle (StyleX override) |
Theme-level, not component-level |
as (polymorphic) |
Rendering detail |
href, target, rel
|
Link behavior, not visual |
Figma Slots (SLOT property type + SlotNode) are a specific Figma feature (currently in open beta) — not a generic named layer. A slot is a frame within a component that allows freeform content editing in instances without detaching. This maps directly to how children and flexible ReactNode props work in React.
Decision matrix:
| Code Pattern | Use Slot | Use Instance Swap | Rationale |
|---|---|---|---|
children?: ReactNode |
✅ | Content is freeform — could be anything | |
items: ReactNode[] (repeating) |
✅ | Instances can add/remove items in the slot | |
icon?: ReactNode |
✅ | Content is always a single component of a known type | |
endContent?: ReactNode |
✅ | Could be a badge, icon, text, or anything | |
startContent?: ReactNode |
✅ | Same as endContent — freeform | |
trigger?: ReactNode (e.g. Popover) |
✅ | Trigger could be button, link, or custom element | |
header?: ReactNode |
✅ | Freeform layout area |
Rule of thumb: If the code prop accepts arbitrary JSX children that could be multiple elements, use a Slot. If it expects a single component of a constrained type (like an icon), use Instance Swap.
Slots are a first-class component property type in Figma's Plugin API. The agent MUST use the native Slot API — never approximate slots with plain named frames.
The simplest way. Call createSlot() on a ComponentNode to create a SlotNode child and automatically register a SLOT property on the component.
// Inside a use_figma call on a ComponentNode
const comp = figma.createComponent();
comp.name = "size=md, variant=primary";
comp.layoutMode = "VERTICAL";
comp.primaryAxisSizingMode = "AUTO";
comp.counterAxisSizingMode = "AUTO";
// Create a slot — this creates both the SlotNode AND the SLOT property
const slot = comp.createSlot();
slot.name = "Content"; // visible in the Layers panel
slot.layoutMode = "VERTICAL";
slot.primaryAxisSizingMode = "AUTO";
slot.counterAxisSizingMode = "FILL";
slot.itemSpacing = 8;
// Optionally add default content inside the slot
await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
const placeholder = figma.createText();
placeholder.characters = "Add content here";
slot.appendChild(placeholder);
return { compId: comp.id, slotId: slot.id };Use this when you need to name the SLOT property explicitly or set preferred instances upfront.
const slotKey = comp.addComponentProperty('Content', 'SLOT', '');Note: addComponentProperty with type 'SLOT' does NOT accept a defaultValue — pass an empty string. The slot's default content is whatever layers are inside the SlotNode in the main component.
// Set preferred instances (guide designers toward recommended components)
comp.editComponentProperty('Content#1:0', {
preferredValues: [
{ type: 'COMPONENT', key: 'abc123' },
{ type: 'COMPONENT_SET', key: 'def456' }
],
description: 'Add action buttons, cards, or other content'
});editComponentProperty for SLOT supports:
-
name— rename the slot property -
preferredValues— set suggested components (array ofInstanceSwapPreferredValue) -
description— help text shown to designers
Does NOT support for SLOT:
-
defaultValue— default content is the layers inside the SlotNode, not a property value
A SlotNode behaves like a FrameNode — it supports:
- Auto-layout (
layoutMode,itemSpacing, padding, alignment) - Fills, strokes, effects, corner radius
- Min/max width/height constraints
- Variable bindings
- Children (any node type)
-
resetSlot()— resets slot content back to main component default
It does NOT support:
- Layout grids (
cannotApplySlotPropertyToFrameWithGriderror) - Being the top-level layer of a component (must be nested)
- Component properties on layers directly inside the slot of a main component (but component instances placed INTO a slot in an instance DO retain their own properties)
// Find all slots in a component
const slots = comp.findAll(n => n.type === 'SLOT');
// Check component property definitions for SLOT type
const propDefs = comp.componentPropertyDefinitions;
const slotProps = Object.entries(propDefs)
.filter(([key, def]) => def.type === 'SLOT');// WARNING: Destructive — resets all instance slot overrides
comp.deleteComponentProperty('Content#1:0');- ❌ Using a plain Frame named "Slot" — this is NOT a slot. Users can't add content without detaching.
- ❌ Using Instance Swap for freeform content — instance swap restricts to a single component. Slots allow any content.
- ❌ Adding component properties to layers inside a slot in the main component — they'll be stripped. Use variable bindings instead, or let inserted instances carry their own properties.
- ❌ Putting a slot at the top level of a component — slots must be nested within a component, not the root layer.
For each component:
- Find the exported props interface (
export interface Astryx{Name}Props) - Parse each field's type:
- Literal union → variant axis with those values
-
boolean→ boolean property -
string→ text property -
ReactNode(single, constrained type likeicon) → instance swap (INSTANCE_SWAP) -
ReactNode(freeform content likechildren,endContent) → Figma Slot (SLOT) - Everything else → skip
- Also find the variant/size type aliases (e.g.,
ButtonVariant = keyof ButtonVariantMap) and resolve to concrete values - Note default values from the function signature destructuring
{
"lastRunAt": "2026-04-22T10:00:00-07:00",
"lastCodeSHA": "abc123def",
"components": {
"Button": {
"figmaNodeId": "201468:12345",
"figmaPageId": "201468:404593",
"lastSyncedAt": "2026-04-22T10:00:00-07:00",
"lastSyncedCodeSHA": "abc123def",
"status": "synced",
"agentState": {
"variantAxes": {
"variant": ["primary", "secondary", "ghost", "destructive"],
"size": ["sm", "md", "lg"]
},
"booleanProps": ["isDisabled", "isLoading", "isIconOnly"],
"textProps": ["label", "tooltip"],
"instanceSwapProps": ["icon"],
"slotProps": ["children", "endContent"]
},
"humanOverrides": []
},
"Toast": {
"figmaNodeId": null,
"status": "missing",
"lastSyncedAt": null,
"lastSyncedCodeSHA": null
}
},
"pendingConflicts": [],
"seenCommentIds": ["123456", "789012"],
"runLog": []
}Before any code diffing, the agent reads new comments on the Figma library file. This surfaces designer feedback that may be relevant to the sync work or needs human attention.
GET https://api.figma.com/v1/files/c9CR0F4jJQwhue60Ec7qOz/comments?as_md=true
Authorization: Bearer {FIGMA_API_TOKEN}
Returns an array of Comment objects:
{
"id": "123456",
"message": "The ghost variant hover state looks off",
"created_at": "2026-04-29T14:30:00Z",
"resolved_at": null,
"user": { "handle": "Ruby Cheung", "id": "..." },
"client_meta": { "node_id": "201468:12345", "node_offset": { "x": 100, "y": 200 } },
"parent_id": null,
"order_id": "1"
}- Only process comments where
resolved_atisnull(unresolved) - Only process comments where
created_atis afterlastRunAtfrom state - Skip comments whose
idis inseenCommentIdsin state - After processing, add all seen IDs to
seenCommentIds
Use client_meta.node_id to identify which component the comment is on. Match against the known Figma node IDs in the state file's components.{name}.figmaNodeId. If a comment is on a node that's a child of a component set, walk up to find the parent component.
Comments are classified into three categories with distinct handling:
1. Questions → Answer them
If a comment is asking a question about the component ("Should this use a slot?", "What's the default size?", "Is there a ghost variant?"), the agent replies with a factual answer derived from the code. The code is the source of truth — cite the specific prop interface, default value, or variant list from Astryx{Component}.tsx.
Example comment: "Does Button support an xl size?"
Agent reply: "Currently Button supports sm, md, and lg sizes
(defined via ButtonSize in packages/core/src/Button/Button.tsx,
line 135). There is no xl variant in code."
The agent MAY reply to questions via POST /v1/files/:key/comments with comment_id set to the parent comment (threaded reply). Keep replies factual, concise, and always cite the code source.
2. Requests not in the codebase → Do not action, surface only
If a comment requests something that doesn't exist in code ("Can we add a compact density?", "We need an xl size"), the agent does NOT take action. Code is king — Figma should reflect the code, not the other way around. Include the request in the GChat summary so a human can decide whether to implement it in code first.
GChat summary:
📋 Feature request (no code match):
• @ruby on Button: "Can we add compact density?" — no compact/density prop in code
3. Parity comments → Action them
If a comment points out that the Figma component doesn't match what's in the code ("The code has a ghost variant but Figma doesn't", "Button has endContent in code but not in Figma", "The sm size padding looks wrong compared to the token"), this is a parity issue. The agent should:
- Verify the claim by reading the code
- If confirmed, treat it as a sync action during STEP 4 (same as a detected code drift)
- Prioritize it — a human noticed the gap, so it's higher signal than automated detection
- Log the comment as the reason for the change in the run report
GChat summary:
✅ Parity fix (from comment):
• @cindy on Button: "code has ghost variant, Figma missing" — confirmed, added ghost variant
| Signal | Classification | Action |
|---|---|---|
Contains ? or starts with "is", "does", "what", "how", "why", "can" |
Question | Answer from code |
Requests something not in the code ("add", "we need", "can we get", "would be nice") AND the feature doesn't exist in Astryx{Component}.tsx
|
Request (no code match) | Surface in GChat, do not action |
| Points out a gap between code and Figma ("code has X but Figma doesn't", "this doesn't match", "missing", "wrong", "out of date") AND the code confirms the discrepancy | Parity | Fix in Figma, cite comment |
| Ambiguous | Surface in GChat | Let human classify |
- Questions: Reply via Figma comments API (threaded reply). Keep factual and cite code.
- Requests: Do not reply in Figma. Surface in GChat only.
- Parity: Reply in Figma after fixing ("Fixed — added the ghost variant to match code"). Then resolve is up to the human.
- Never resolve or delete comments — that's for humans.
Before running any Figma operations, the agent must verify that:
- The Mac CLI node is connected
- Claude Code is available on the Mac
- The Figma MCP server is connected and responding
If any of these fail, the agent:
- Sends a message to GChat: "Figma Librarian: Can't reach Figma MCP. Is Figma open with the MCP server connected? Skipping today's run."
- Logs the skip in state
- Does NOT attempt partial work
The nightly detection step (reading code changes) can still run on the sandbox without Figma.
These components in packages/core/src/ have no meaningful Figma representation:
| Component | Reason |
|---|---|
| Layer | z-index management utility |
| SizeContext | React context provider |
| Field | Internal form field wrapper |
| Center | Layout utility |
| Stack | Layout utility |
| Layout | Page layout utility |
| Grid | Grid layout utility |
| AspectRatio | Ratio container utility |
The agent maintains this list in state and skips these automatically. If a new component is added that looks like infrastructure (no visual props, no variant types, only children + utility props), classify it as NO_FIGMA_IMPACT and add to skip list.
For NEW_COMPONENT builds, the agent invokes the CC Figma Builder skill's 4-phase workflow. Key rules from that skill:
-
Reuse existing sub-components — never recreate Button, Icon, etc. Use
figma.root.findOne()orimportComponentByKeyAsync() - Icons from XMDS Meta Icons Library — never draw manually
-
Always bind variables — never hardcode hex/spacing values. Use the known Variable IDs:
- Colors: accent(1:3), bgSurface(1:7), textPrimary(1:13), border(1:34), etc.
- Spacing: 0(1:652) through 10(1:664)
- Size: SM/28(1:668), MD/32(1:669), LG/36(1:670)
- Radius: None(1:674), Inner(1:675), Element(1:676), Container(1:677), Full(1:679)
-
Use native Figma Slots for ReactNode/children props — call
comp.createSlot()oraddComponentProperty('Name', 'SLOT', ''). Never use a plain named frame as a fake slot. See the Figma Slots API Reference section above for the full API. -
Component sizing: use
'HUG'forlayoutSizingHorizontal/layoutSizingVertical— set AFTERappendChild - Screenshot verification is MANDATORY after every build
Every new component MUST be built inside a Component Frame — never as a loose node on the page. This is the standard wrapper structure used by every existing component in the library.
Component Frame template: Node 266:35317 on the Components page. Clone it and rename.
Structure:
FRAME "Astryx{ComponentName}" (1600 × auto, VERTICAL layout)
├── INSTANCE "Document Header #quality_disable" ← title/description header
└── FRAME "Body" (VERTICAL layout)
└── COMPONENT_SET "{ComponentName} / Astryx{ComponentName}"
How to create a new component's frame via Plugin API:
// 1. Clone the template frame
const page = figma.root.children.find(p => p.name === 'Components');
await figma.setCurrentPageAsync(page);
const template = await figma.getNodeByIdAsync('266:35317');
const compFrame = template.clone();
compFrame.name = 'Astryx{ComponentName}';
// 2. Position in the correct category row
compFrame.x = {column} * 1700; // columns at x = 0, 1700, 3400, 5100, 6800
compFrame.y = {row_y}; // y from the category section
// 3. Build component set inside the Body frame
const body = compFrame.findOne(n => n.name === 'Body');
body.appendChild(componentSet);Sub-components (e.g., ChatMessage, CommandPaletteItem) go inside the SAME Component Frame as their parent component — not in a separate frame. The Body frame holds both the main component set and any sub-component sets.
Components are organized in a grid on the Components page (3:12315). Each category has a header frame, followed by rows of Component Frames at x = 0, 1700, 3400, 5100, 6800 (1600px wide + 100px gap).
New components MUST be placed in the correct category row, matching the ordering in the code's packages/core/src/index.ts export order:
| Category | Section Header ID | Row y-positions | Existing components |
|---|---|---|---|
| Call to Action | (top of page) | y≈254 | Button, Link, SegmentedControl, ToggleButton |
| Communication | 86:350115 |
y≈4008, y≈7208 | Banner, Tooltip, Popover, HoverCard, Dialog, EmptyState |
| Containers | 86:350117 |
y≈10962, y≈14200 | Card, Section, Collapsible, AppShell, FormLayout, AspectRatio, Carousel |
| Chat | 190:8652 |
y≈18208 | Chat |
| Data Display | 86:350119 |
y≈23791..30191 | Text, Heading, Divider, StatusDot, CodeBlock, Markdown, Avatar, Table, Timestamp, Kbd, Token, Badge, MetadataList, Icon |
| Lists | 86:350121 |
y≈33945 | List, TreeList, OverflowList |
| Inputs | 86:350123 |
y≈37699..50499 | TextInput, TextArea, NumberInput, CheckboxInput, Switch, RadioList, Selector, MultiSelector, DateInput, TimeInput, Calendar, Tokenizer, Typeahead, Field, DropdownMenu, MoreMenu, PowerSearch |
| Navigation | 86:350125 |
y≈54253..60653 | TopNav, SideNav, Breadcrumbs, TabList, Pagination, MobileNav, Toolbar |
| Performance | 86:350127 |
y≈64407 | Spinner, ProgressBar, Skeleton, Toast |
To find the next available position in a row: scan existing frames at that y-position, find the max x, add 1700.
- Check if all sub-component dependencies exist in Figma (BLOCKING)
- If a dependency is missing, build it first (bottom-up)
- Clone the Component Frame template (
266:35317) and position it in the correct category row - Build the component set using incremental
use_figmacalls, placing it inside the Body frame - Verify with
get_screenshotvs Storybook reference - Fix discrepancies before marking as complete
- Cron: Once per day at 12pm PST, Mon–Fri
- Business hours only — inverted from other Night Watch roles (this role NEEDS the laptop)
- Idempotent: If the laptop was off for several days, the first run back processes all accumulated changes in one pass
- Early exit: If no code changes since last run, log "no changes" and exit. Don't read Figma unnecessarily.
- Rate limit awareness: Complex new component builds may take a while. Track partial builds in state and resume on the next day's run.
After each run, post to the notification space:
Figma Librarian — {date}
Figma comments (new since last run): {N}
Answered:
• @ruby on Button: "Does it support xl?" → replied with code reference
Parity (actioned):
• @cindy on Card: "code has ghost, Figma missing" → fixed
Requests (no code match — needs human):
• @joey on Dialog: "Can we add a compact size?" — no compact prop in code
Components checked: {N}
Updates applied: {N}
• Button: added size="xl" variant
• Toast: new component built (full build)
Conflicts flagged: {N}
• Card: padding differs from code default — human adjusted?
Human overrides preserved: {N}
• Banner: custom icon slot sizing kept
Skipped (no Figma impact): {N}
Next run: {time} or when laptop is back online
If nothing changed:
Figma Librarian — {date}
No code changes or new comments since last run. All components in sync. ✓
The first run has no prior AGENT state. The agent must:
- Read ALL components from code (
packages/core/src/*/Astryx*.tsx) - Read ALL components from Figma (via
use_figmadiscovery script) - Match them by name (code
Button→ FigmaButtonorButton) - For each matched pair: record current FIGMA state as AGENT state (assume current Figma is correct)
- For unmatched code components: mark as
missingin state - For unmatched Figma components: ignore (Figma may have components that don't map 1:1 to code)
This means the first run is read-only — it establishes the baseline without modifying anything. Actual sync starts on the second run.
- Night Watch Overview — Shared principles and constraints
- Night Watch Roles — All role assignments
- Night Watch Adding Roles — How this page was created
- CC Figma Builder Skill —
xds-figma-libraryCC skill for the 4-phase build workflow - Astryx OSS Figma Library — The Figma file being maintained