Skip to content

Spec Layer v7.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 14 Oct 11:08
· 11 commits to main since this release
b57901f

Plugin 7.0.0 is a major release because every existing document asks to
be updated once. EXTRACTOR_VERSION moves from '3' to '4' for Figma
slots, so a component doc built by 6.0.0 or earlier reads "Rebuild
needed". Annotate in Dev Mode, motion variables and the Motion section,
Figma slots, placeholders for empty writing sections and the Library's
incremental source check all ship here, with Foundation Context v5 schema
5.2.0 and Component Context v5 schema 5.4.0. It is the first plugin built
and attested by release-plugin.yml. The proxy it needs deploys from
main ahead of it, and spec-layer@0.13.0 is latest on npm, which a
pulled Markdown page needs before it says what the canvas says about a
slot.

The CLI portion of this section ships as 0.12.0, a minor release because
pull, status and list gain --json, and because --help and
--version now print to stdout and exit 0. It is also the first CLI that can
pull a library with an EASING variable: 0.11.0 does not know the easing
token type, refuses the Foundation as failing schema validation, and asks for
a republish that cannot help. A plugin build that exports easing tokens must
not reach the listing before 0.12.0 is latest on npm. A repository on 0.11.0
re-projects once on its first pull with 0.12.0 through the
manifest.cliVersion check. 0.12.0 is the first version published by the
release workflow, so it is the first to carry npm provenance.

The CLI changes merged after 0.12.0 ship as 0.13.0, a minor release: pulled
Markdown prints each Figma slot's facts (preferred components, "only
preferred", the limits and default content) where 0.12.0 lists a slot with
its type alone, and spec-layer skill says what a slot asks of code (Figma
slots, under Added). A plugin build that documents slots should not reach the
listing before 0.13.0 is latest on npm, so a pulled Markdown page says what
the canvas says.

Added

  • Annotate in Dev Mode. A Library row's menu and Settings, Export can now write a
    component doc back into Figma's own fields, so Dev Mode and the REST API
    show what the canvas shows. The Usage text the doc shows (Overview, When to
    use, When not to use) goes into the component description with one
    provenance line, and is cleared again once the doc no longer has it. When a
    file link is saved in Settings, the documentation link opens the doc's
    Section, follows it when an Update rebuilds the Section, and is cleared when
    the doc is deleted. When the doc draws its Anatomy legend, each top-level
    part gets a native annotation numbered as the legend numbers it, with
    padding, spacing, radius and type pinned as live properties, so no number is
    ever typed; an annotation on a part the legend no longer numbers is removed,
    and a doc whose parts changed since it was built places no annotation until
    it is updated. Every run you start asks first and names AI text nobody has
    edited: the confirmation says what it writes in one line and lists each
    caution as its own point, naming which kinds of value (description, link,
    annotations) a change made in Figma keeps. A description, link or annotation set, changed or removed in Figma
    is kept, and replacing it is a separate, labelled step that also names AI
    text: it is offered once, when a run finds a value newly changed in
    Figma, and again from a component's Library row; a run over every
    component names what it kept in a toast instead. A part shown by a
    property is labelled "Shown when … is true", as the legend says. A record
    of what was written lives in the component's plugin data,
    and the serializer reads a description or link that matches it as absent,
    so an annotated doc never reports drift against its own words and the AI
    prompt is never fed the sync's own text. A description a person edits in
    Figma becomes theirs and reads as source like any other, without the
    provenance line. A record from a newer build is left untouched. Nothing it
    writes enters a hash or an artifact, and EXTRACTOR_VERSION is unchanged.
    An optional switch annotates again after an Update, only for components
    already annotated by hand, never writes text that was edited in Figma or
    written with AI, and names a component it held back once, not on every
    Update. Whether Figma's MCP server or Make reads the descriptions is not
    verified.

  • Motion variables in the Foundation. Figma's TIMING and EASING
    variable types (Plugin API update 133) are read as duration tokens in
    seconds and a new easing token type (a named preset, a cubic bezier, a
    spring bounce, or hold). Foundation rows read "0.3 s" or the easing name,
    and a cubic bezier draws its curve. Foundation Context v5 moves to schema
    5.2.0 (foundation-5.2.0.json); the envelope sits outside the content
    hash, so no artifact identity moves for a file without these variables.
    DTCG projects duration and a cubic bezier cubicBezier; a preset,
    spring or hold is omitted and named in report.json, never expanded to
    numbers the API did not return. Before this, a TIMING variable exported
    as a unitless number and an EASING variable read "Not resolved".
    @figma/plugin-typings moves to 1.140.0.

  • Motion section for variant transitions. Prototype interactions that
    change a component set's variant (hover, press, click, delay) appear as a
    Motion section after States, with From, To, Trigger, Transition, Duration
    and Easing, and as motion.transitions in the YAML brief, Component
    Context v5 (since schema 5.3.0), Markdown and the AI
    profile. A CHANGE_TO outside the set is reported as
    transition-target-outside-set. The section is marked Beta in the
    section picker; existing documents gain it when it is ticked on. The
    motion key exists only when a component has transitions, so every
    document without them keeps a byte-identical hash: the content hash of an
    affected document moves on its own, which is what drift detection
    reports. (The release still moves EXTRACTOR_VERSION to '4', for Figma
    slots, below.) An easing on a prototype
    transition that Figma stores as a physical spring (mass, stiffness,
    damping) is reported as not supported, never converted to a bounce,
    because the conversion is Figma's own and not available to a plugin.

  • Figma slots. A component's slot properties and slot layers are read
    and documented. Each slot is a property under api.slots with
    type: slot, and Figma's own facts about it: the designer's
    description, the preferred components (named from the instances inside
    the component, then the file's own components, and never over the
    network, so a drift check does not depend on library access), "only
    preferred", the minimum and maximum, "display empty
    by default" and "stretch child on insert". Each slot layer is one anatomy
    part carrying slot (its property) and default_content, what it holds
    in the default variant. Default content is an example, so it is no longer
    documented as anatomy parts, related components, token bindings or
    unbound values; a slot inside a nested instance keeps its content, which
    is fixed content of the component. On canvas, Properties reads Type
    Slot with the preferred names, "only these", the limits and the
    default content, and the Anatomy legend marks each slot. A preferred key
    the document cannot name is counted, never named, and reported as
    unresolved-slot-content; a slot linked to no property is reported as
    unlinked-slot; a property type the extractor does not know is left out
    and reported as unsupported-property-type. The Library's change list
    itemizes each slot change. Component Context v5 schema 5.4.0
    (component-5.4.0.json) gains anatomy_node.slot,
    anatomy_node.default_content and a typed api.slots entry. 5.3.0,
    the motion contract above, was published on spec-layer.com before
    slots and stays there unchanged; no release writes it.
    EXTRACTOR_VERSION moves to '4', so every document built by 6.0.0
    reads Rebuild needed once. Annotate in Dev Mode never
    pins a corner radius on a slot, which Figma refuses. Each api.slots slot
    entry also carries default_content, so the Markdown page prints the same
    Values and Default text as the canvas, adds a Description column only for
    a slot's own description, and says in Anatomy what each slot holds. A
    published library versions a slot by direction: narrowing what may go in
    it (only preferred turned on, a raised minimum, a lowered or new maximum, a
    preferred component removed while only preferred is on) is major,
    widening is minor, and its default content and display settings are
    patch; each is a property change in the history, named by its facet, so a
    6.0.0 plugin's history pane still reads it. The AI writing prompt gets
    each slot's facts in the same words. An instance swap property now reads
    "Instance swap" in Markdown rather than "InstanceSwap". The agent guide
    spec-layer skill writes, and the plugin's skill download, say what a
    slot asks of code: build it as the framework's children or a named slot,
    accept only the preferred components when only preferred is on, keep to
    the limits, and treat default content as an example, never as fixed
    markup. The guide uses the YAML keys or the Markdown page's words,
    whichever format the components were written in.

Changed

  • CI's dependency review exempts @figma/plugin-typings from the licence
    allow-list. The package ships a verbatim MIT licence but declares it as the
    non-SPDX string "MIT License", which GitHub reports as
    LicenseRef-bad-mit-license, so every version bump failed the gate. The
    terms are unchanged; only the metadata is exempted.

  • The component screen's main button says Update docs (and
    Updating docs… while it runs) when the component already has a doc,
    in place of Replace docs. It runs the same build as the Library's Update.

  • Pro AI writing moves to Claude Sonnet 5.5. The proxy now assigns
    claude-sonnet-5-5 to a proved Pro license, still at
    output_config.effort: low, in place of claude-sonnet-5. The per-token
    price is the same. The free plan stays on Claude Haiku 4.5
    (claude-haiku-4-5), which is still the current Haiku. The plugin never
    names a model, so this needs only a proxy deploy, and every 6.0.0 plugin
    picks it up. A 5.1.0 plugin's legacy request still names its own Haiku
    model and is forwarded unchanged. An answer the proxy stored before the
    deploy is replayed for an unchanged request until it expires, within 24
    hours.

  • The spec-layer CLI is now published from this repository's release
    workflow through npm trusted publishing, so each version from the next
    release on carries an npm provenance attestation that names the commit and
    workflow run it was built from. npm audit signatures checks it. Plugin
    releases attach the built plugin as a zip with a GitHub build provenance
    attestation (gh attestation verify).

  • The proxy's license routes refuse a request body over 4 KB with 413,
    like the other routes that read a body. Every proxy response carries
    X-Content-Type-Options: nosniff, and an internal error's log line
    carries the first 2,000 characters of its stack; the response stays
    generic.

  • The proxy's per-IP rate limits now hold across every isolate in a
    Cloudflare location: behind each isolate's own window sits the Workers
    Rate Limiting binding, at the same limits (60 a minute for prose, quota,
    dry runs and pulls; 20 for license, publish and rotate). Before, each
    isolate counted alone, so a burst spread across isolates was not limited.
    Bundles are hashed on publish with the runtime's native SHA-256.

  • spec-layer pull, status and list take --json, which prints one
    JSON object instead of the text: the command, its exit code, its own
    fields (state, version, publish date, the files written), and the text
    lines as stdout and stderr arrays. Exit codes do not change.
    spec-layer --help and --version now print to stdout and exit 0; before,
    both printed the usage to stderr and exited 1.

  • pull and status now retry a dropped connection, a timeout, a 5xx or
    a 429 twice with a short backoff, honouring the server's Retry-After
    up to 30 seconds, and say so on stderr. SPEC_LAYER_RETRIES=0 turns this
    off. A failed connection names the system error (ENOTFOUND, a
    certificate error) and, when HTTPS_PROXY is set without
    NODE_USE_ENV_PROXY=1 or a certificate is not trusted, what to set.
    Requests carry a User-Agent with the CLI and Node versions, and a
    response over 64 MB is refused instead of read.

  • When the free AI writing allowance is used up and AI writing is on, the
    component screen now says so before you build: one line under the switch
    reads that no free AI uses are left until the reset date and that sections
    marked AI will be drawn as placeholders, with an Upgrade to Pro action.
    Before, the header knew and the build went ahead, and the placeholders
    arrived as news in a toast afterwards.

  • "Detach this doc" in a Library row menu has its own glyph. It shared one
    with "View this doc on canvas".

  • The Library's Update all progress line reads "Updating docs" and leaves the
    number to the count beside the bar, which said "0 of 3" while the label
    said "doc 1 of 3".

  • Quick search no longer repeats the page name on every row when every result
    comes from the same page.

  • Coming back to the Library from another tab no longer re-runs the source
    check on every visit. The plugin now watches the current page for edits
    and re-checks only when something changed since the last scan; an
    unchanged file sends no extraction work at all. When a check does run it
    goes one document at a time in the order the rows are shown, pauses when
    you leave the Library and resumes when you return, and shares one
    variable and style lookup cache across the whole pass instead of one per
    document. One line under the filters shows a running check's progress
    ("Checking for source changes", a bar, "3 of 14") and, once it
    finishes, when the rows were last checked ("Checked just now", "Checked
    4 min ago", "Checked at 10:42"). It keeps the same height in every
    state, so a check starting or finishing no longer moves the list, and
    the progress no longer floats over the last rows; Update and Update all
    docs keep their progress above the footer. A doc built by an older
    version of the plugin reads "Rebuild needed" as soon as the rows appear,
    with no check to wait for, so the rebuild banner no longer drops in
    above the list partway through, and those docs skip a source read that
    could only ever say that. Refresh library still forces a full check. A
    variable rename, addition, or deletion also counts as a change. A spacing or radius
    (number) variable value edit also counts, since a component's layout
    summary carries the resolved numbers. Any style edit counts, a paint,
    text, effect or grid value edit included, whichever page you make it
    on, so a Foundation row that shows a style re-checks after it. A color,
    string or boolean variable value edit does not, because no component row
    carries those values and Figma sends no event for variables, and the
    Foundation row that shows them keeps its last result until the next
    change or Refresh library. The plugin watches layer edits on the page you
    have open only, so layer edits by others on any other page are not seen,
    visited or not.
    While the plugin is asking whether anything changed, Update, Update all
    docs and Refresh library are disabled for that moment. A source check
    still running when a new scan starts no longer sets a row from a read
    taken before that scan; each reply carries the pass it was asked for. No
    extraction output changes.

  • Free AI writing is now a flat 20 generations per UTC calendar month. It
    was 20 in the first 30 days after first use and then 10 a month; the
    first-sight boost window is gone from the proxy's quota engine, and its
    two stored fields (firstSeen, boostUsed) are dropped when a counter
    loads. Every generation was already counted by month, so a free user
    keeps this month's count and moves straight to the new limit, with no
    migration. This is a proxy change only: the plugin reads the limit from
    the proxy's quota headers, so no plugin release is needed. Publishing
    limits are unchanged.

  • Empty writing sections are drawn as placeholders instead of left out.
    When a selected writing section has no prose (AI writing off, the monthly
    allowance spent, or a failed request), the doc now draws it inside a dashed
    box marked Placeholder, with guidance on what to write, in the section's
    own shape: When to use, Do and don't, Keyboard, Pointer and touch, Semantics
    and focus, Content, and Overview when the component has no Figma
    description. Type over the guidance on the canvas and the next Update keeps
    it and drops the box. Guidance is never stored as prose, so Copy for AI,
    the YAML and Markdown outputs and publishing never see it. Sections built
    from the spec that have nothing in it are still left out. The result line
    now reads "Added placeholders for When to use, Keyboard. Fill them in on
    the canvas.", and the allowance note says sections that needed AI were
    added as placeholders. No hash or EXTRACTOR_VERSION change.

    Text typed into a placeholder is recorded as written by a person, and
    stays recorded after later Updates. The brief's and the Component Context
    v5 artifact's guidelines block lists those fields under authored, after
    origin: generated, or reads origin: authored when a person wrote every
    field in it; a field built partly from AI text, such as Interactions with a
    typed Keyboard row beside AI-written Pointer bullets, is not listed. The
    Markdown marks a section a person wrote "Written on the Figma canvas, not
    generated by AI." in place of the AI line. A rebuild never asks AI to
    rewrite a key a person wrote, Keyboard included. The spec-layer skill
    guide describes both markers. Output for docs without typed-in text is
    byte for byte unchanged, and guidelines stays outside every hash, so no
    schema file changes. The documentation site's description of guidelines
    lives outside this repository and still has to be updated there.

  • Faster builds, Updates and selections. Clicking from layer to layer
    inside a component set no longer reads the whole set again: a selection
    that resolves to the component the panel already shows, unchanged, sends
    nothing, so the panel also keeps the variants and sections you picked. It
    reads again after a layer edit on the current page, any style edit, or a
    variable change. Edits by others on other pages are not seen, the same
    limit the Library's check has. The first selection reads the component and
    the file's variables at the same time instead of one after the other.

    An Update no longer re-checks every doc in the file when it finishes. The
    docs it rebuilt are in sync, and every other row keeps the result of its
    last check, since an Update never changes a source; "Checked ..." keeps
    that check's time. Refresh library still checks every row. Update all now
    rebuilds each doc without reading the whole registry for it, and shares
    one variable lookup cache, one Foundation read and one contrast report
    across the run.

    Token chips resolve each token once per build instead of once per variant
    card, and a token table resolves all of its tokens together. A Foundation
    frame loads its text-style fonts at once. Telling whether a doc has hand
    edits finds its tagged layers with one search instead of asking every
    layer, so the Library scan and every build spend less time there. None of
    this changes a hash or EXTRACTOR_VERSION.

  • Removed code nothing reached: the Markdown token tables left in the
    extractor after the 1.x Markdown specification went, narrowFoundation
    and the not-in-scope reference status only it could produce, and four
    test-only v5 helpers. The extractor and the CLI now share one canonical
    JSON serializer; every hash is byte for byte unchanged.

Fixed

  • A component with a Figma slot property no longer stops its Properties
    section from building. The extractor did not know Figma's SLOT
    property type, so the property reached the canvas with no type, and
    Figma refuses an empty text value. A baseline saved before this fix lists
    the change as "type not recognised changed to slot".

  • Undo in Figma now reverts one Spec Layer action at a time. Plugin edits
    were never closed as undo steps, so one Undo after building a second doc
    could take the first doc away too. Each build, Foundation build, update,
    detach and remove is now its own step.

  • Keyboard focus stays where it was when the plugin redraws on its own (a
    build finishing, the allowance loading, a selection change); before, it
    fell back to the page and a screen reader lost its place. The plugin also
    follows a change of Figma's light or dark theme while it is open, and an
    invalid first version on Publish is read out with its hint.

  • An error no part of the plugin expected is now logged to the plugin
    console and shown as a toast, instead of stopping the action silently.

  • A spec-layer pull that could not move its new output into place (on
    Windows, a file held open by a virus scanner or indexer) deleted the
    previous .speclayer/ first and left none. The previous copy now moves
    aside first and moves back when the swap fails. Rotating the pull key
    over a loose-permission speclayer.local.json no longer leaves the new
    key readable for a moment: the file is made 0600 before the key is
    written. speclayer.json is written atomically.

  • AI writing no longer fails after the text was already written. When the
    proxy wrote an answer but could not record it against the allowance, it
    answered with an error, and every retry for the next three minutes was
    told a generation was still pending. The answer now comes back, is not
    counted, and a retry runs at once.

  • A Pro license check now gives up on Lemon Squeezy after 10 seconds and
    falls back to the last good result within the 5-day grace window. Before,
    a request waiting on a Lemon Squeezy call that never answered stayed open
    until Cloudflare ended it.

  • When Anthropic is overloaded or rate limited (429, 503 or 529), the proxy
    now answers 503 upstream_busy and passes on Anthropic's Retry-After
    when there is one, instead of a 502 that read as broken. The plugin
    shows the same message for both.

  • A damaged license cache entry is now treated as missing and replaced,
    instead of failing every request for that key with a 500.

  • Clicking inside the component you just documented, then pressing Create
    docs again, asked for a new AI draft and spent another free AI use. The
    draft is now kept for that component as long as it reads exactly as it
    did when the draft was written.

  • The Measurements card, its value chips, and the "No variant" cell in a
    matrix kept rounded corners under the Sharp and Round corner styles. They
    now follow the theme like the Anatomy card beside them.

  • "Contact support" on the License screen opened the homepage. A key the
    service has turned off now says which address to email and the button,
    Email support, opens your mail client with the subject filled in.

  • A component build that fails now stays on screen as a banner above the
    footer until the next Create docs or selection, the way a Publish error
    already does, instead of a five-second toast. This covers a failure before
    anything reaches the canvas too (no section picked). Reselecting the same
    component keeps the banner, and selecting another layer clears it. If the
    selection changes right as the build fails, for example because a layer
    was deleted during it, the failure arrives as a toast instead.

  • The Publish screen said "Checking…" three times while it read the file's
    identity. The header pill and the footer button say it; the Version block
    waits.

  • Building a Foundation frame no longer fails with "in set_maxWidth: Can
    only set maxWidth on auto layout nodes and their children". 6.0.0 capped
    two text nodes before appending them to their auto-layout parent, which
    Figma refuses: a code-syntax identifier chip, so any collection with a
    variable that defines code syntax, and the block label on a frame that
    holds both layouts. Both caps are now set after the append, and the
    test stub throws Figma's own error when a min or max size is set outside
    auto layout, so the suite catches this order from now on.

Before publishing

  • The manual Figma pass in packages/plugin/TESTING.md ran on this build (the zip attached here) and is recorded below.
  • npm run check:site-live passed against https://spec-layer.com (the scheduled Site live run counts).
  • The proxy this build needs is deployed to production.
  • Any CLI version this build needs is latest on npm.
  • The Figma Community listing is updated to 7.0.0 last, after all of the above.

Install from the Figma Community listing, or import manifest.json from the attached zip under Plugins → Development → Import plugin from manifest. gh attestation verify <zip> --repo sandroleks/spec-layer checks the zip was built by this repository's release workflow.