diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0a2047e9..be7eea74 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,7 +2,7 @@ name: build
on:
push:
- branches: [main]
+ branches: [main, 'ci/**']
pull_request:
branches: [main]
@@ -20,6 +20,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v6
@@ -31,6 +33,12 @@ jobs:
cache: 'pnpm'
cache-dependency-path: pnpm-lock.yaml
+ - name: Check agent plugin version
+ if: matrix.node == '24.x'
+ env:
+ PLUGIN_VERSION_BASE: ${{ github.event_name == 'pull_request' && 'HEAD^1' || startsWith(github.ref, 'refs/heads/ci/') && 'origin/main' || github.event.before }}
+ run: node scripts/check-agent-plugin-version.mjs
+
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -38,6 +46,7 @@ jobs:
run: pnpm --filter @tempad-dev/extension test:setup
- name: Check agent plugins
+ if: matrix.node == '24.x'
run: >-
pnpm agent-plugin:build &&
test -z "$(git status --porcelain --untracked-files=all --
@@ -77,7 +86,39 @@ jobs:
run: pnpm format:check
- name: Check bridge compatibility
- run: pnpm mcp:check-bridge
+ run: node packages/mcp-server/scripts/check-bridge-compatibility.mjs
+
+ - name: Check runtime profile isolation
+ run: node packages/mcp-server/scripts/check-runtime-profiles.mjs
+
+ - name: Check CLI host context isolation
+ run: node packages/mcp-server/scripts/check-host-context.mjs
+
+ - name: Check isolated runtime supervisor
+ run: pnpm mcp:check-runtime-prototype
+
+ - name: Check local runtime switching and recovery
+ run: pnpm mcp:check-runtime-switch
+
+ - name: Prepare dependency metadata for offline release packaging
+ run: pnpm mcp:prepare-release-dependencies
+
+ - name: Check verified release downloads and explicit activation
+ env:
+ npm_config_logs_dir: ${{ runner.temp }}/tempad-npm-logs
+ run: pnpm mcp:check-runtime-updates
+
+ - name: Preserve failure evidence
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: validation-failures-node-${{ matrix.node }}
+ path: |
+ packages/*/.vitest/attachments/**
+ ${{ runner.temp }}/tempad-npm-logs
+ include-hidden-files: true
+ if-no-files-found: ignore
+ retention-days: 14
typescript-compatibility:
runs-on: ubuntu-latest
diff --git a/.github/workflows/publish-mcp.yml b/.github/workflows/publish-mcp.yml
index 9746448c..7494dbc3 100644
--- a/.github/workflows/publish-mcp.yml
+++ b/.github/workflows/publish-mcp.yml
@@ -15,16 +15,11 @@ on:
permissions:
contents: read
- id-token: write
jobs:
- publish:
- name: Publish @tempad-dev/mcp
+ guard:
+ name: Validate release branch
runs-on: ubuntu-latest
- concurrency:
- group: publish-mcp
- cancel-in-progress: true
-
steps:
- name: Require main branch
env:
@@ -35,6 +30,22 @@ jobs:
exit 1
fi
+ windows-runtime:
+ name: Validate Windows runtime
+ needs: guard
+ uses: ./.github/workflows/windows-runtime.yml
+
+ publish:
+ name: Publish @tempad-dev/mcp
+ needs: [guard, windows-runtime]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ concurrency:
+ group: publish-mcp
+ cancel-in-progress: false
+ steps:
- name: Checkout repository
uses: actions/checkout@v7
@@ -55,16 +66,39 @@ jobs:
- name: Build package
run: pnpm build:mcp
+ - name: Prepare dependency metadata for offline release packaging
+ run: pnpm mcp:prepare-release-dependencies
+
- name: Validate package
run: |
pnpm --filter @tempad-dev/mcp... typecheck
pnpm --filter @tempad-dev/mcp... test:run
pnpm mcp:check-bridge
pnpm --filter @tempad-dev/mcp check:logging
+ pnpm mcp:check-runtime-updates
+ pnpm mcp:check-runtime-switch
+
+ - name: Prepare and verify the immutable release
+ run: pnpm mcp:prepare-release --output "$RUNNER_TEMP/tempad-mcp.tgz" --verify-baseline
+
+ - name: Check the exact release archive
+ run: |
+ release_check_dir=$(mktemp -d "$RUNNER_TEMP/tempad-mcp-installed.XXXXXX")
+ trap 'rm -rf "$release_check_dir"' EXIT
+ tar -xzf "$RUNNER_TEMP/tempad-mcp.tgz" -C "$release_check_dir" --strip-components=1
+ node packages/mcp-server/scripts/check-bridge-compatibility.mjs "$release_check_dir"
+ node packages/mcp-server/scripts/check-log-transport.mjs "$release_check_dir"
+
+ - name: Archive the reviewed release and manifest
+ uses: actions/upload-artifact@v7
+ with:
+ name: tempad-mcp-release
+ path: |
+ ${{ runner.temp }}/tempad-mcp.tgz
+ ${{ runner.temp }}/tempad-mcp.tgz.json
- name: Publish
- working-directory: packages/mcp-server
- run: npm publish --access public --tag "${{ inputs.tag }}"
+ run: npm publish "$RUNNER_TEMP/tempad-mcp.tgz" --ignore-scripts --access public --tag "${{ inputs.tag }}"
- name: Verify published version
working-directory: packages/mcp-server
@@ -72,10 +106,12 @@ jobs:
NPM_DIST_TAG: ${{ inputs.tag }}
run: |
version=$(node -p "require('./package.json').version")
+ expected=$(node -p "require(process.env.RUNNER_TEMP + '/tempad-mcp.tgz.json').integrity")
for attempt in {1..20}; do
published=$(npm view "@tempad-dev/mcp@$version" version --prefer-online 2>/dev/null || true)
tagged=$(npm view "@tempad-dev/mcp@$NPM_DIST_TAG" version --prefer-online 2>/dev/null || true)
- if [[ "$published" == "$version" && "$tagged" == "$version" ]]; then
+ integrity=$(npm view "@tempad-dev/mcp@$version" dist.integrity --prefer-online 2>/dev/null || true)
+ if [[ "$published" == "$version" && "$tagged" == "$version" && "$integrity" == "$expected" ]]; then
echo "Verified @tempad-dev/mcp@$version on $NPM_DIST_TAG"
exit 0
fi
diff --git a/.github/workflows/publish-plugins.yml b/.github/workflows/publish-plugins.yml
index c3bb5d89..d5788317 100644
--- a/.github/workflows/publish-plugins.yml
+++ b/.github/workflows/publish-plugins.yml
@@ -5,16 +5,11 @@ on:
permissions:
contents: read
- id-token: write
jobs:
- publish:
- name: Publish @tempad-dev/plugins
+ guard:
+ name: Validate release branch
runs-on: ubuntu-latest
- concurrency:
- group: publish-plugins
- cancel-in-progress: true
-
steps:
- name: Require main branch
env:
@@ -25,6 +20,17 @@ jobs:
exit 1
fi
+ publish:
+ name: Publish @tempad-dev/plugins
+ needs: guard
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write
+ concurrency:
+ group: publish-plugins
+ cancel-in-progress: false
+ steps:
- name: Checkout repository
uses: actions/checkout@v7
diff --git a/.github/workflows/windows-runtime.yml b/.github/workflows/windows-runtime.yml
new file mode 100644
index 00000000..bb0c2c5a
--- /dev/null
+++ b/.github/workflows/windows-runtime.yml
@@ -0,0 +1,127 @@
+name: Windows runtime
+
+on:
+ push:
+ branches: [main, 'ci/**']
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+ workflow_call:
+
+permissions:
+ contents: read
+
+jobs:
+ runtime:
+ runs-on: windows-latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ max-parallel: 4
+ matrix:
+ node: ['22.x', '24.x']
+ suite: [contracts, prototype, switch, updates]
+ env:
+ PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
+ TEMPAD_RUNTIME_EVIDENCE_DIR: ${{ github.workspace }}/runtime-ci-logs/fixtures
+ steps:
+ - name: Preserve reviewed source bytes
+ run: git config --global core.autocrlf false
+
+ - name: Share dependency metadata across install and offline packaging
+ run: '"pnpm_config_cache_dir=$env:RUNNER_TEMP/tempad-pnpm-cache" >> $env:GITHUB_ENV'
+
+ - name: Checkout repository
+ uses: actions/checkout@v7
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v6
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version: ${{ matrix.node }}
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Prepare dependency metadata for offline release packaging
+ if: matrix.suite == 'updates'
+ run: pnpm mcp:prepare-release-dependencies
+
+ - name: Record runtime environment
+ env:
+ EXPECTED_NODE: ${{ matrix.node }}
+ run: |
+ New-Item -ItemType Directory runtime-ci-logs | Out-Null
+ node -p 'JSON.stringify({platform:process.platform,arch:process.arch,node:process.version,execPath:process.execPath})' | Tee-Object runtime-ci-logs/environment.log
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ pnpm --version | Tee-Object -Append runtime-ci-logs/environment.log
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ $runtimePlatform = pnpm exec node -p process.platform
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ $runtimeVersion = pnpm exec node -p process.versions.node
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ "pnpm runtime: $runtimePlatform $runtimeVersion" | Tee-Object -Append runtime-ci-logs/environment.log
+ if ($runtimePlatform -ne 'win32' -or $runtimeVersion.Split('.')[0] -ne $env:EXPECTED_NODE.Split('.')[0]) {
+ throw 'Unexpected test runtime'
+ }
+ $metadataCache = pnpm config get cache-dir
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ "pnpm metadata cache: $metadataCache" | Tee-Object -Append runtime-ci-logs/environment.log
+ $metadataFiles = @(Get-ChildItem -LiteralPath $metadataCache -Recurse -File -Filter '*.jsonl' | Where-Object { $_.DirectoryName -match '[\\/]metadata-full[\\/]' })
+ "Full metadata files: $($metadataFiles.Count)" | Tee-Object -Append runtime-ci-logs/environment.log
+ $metadataFiles | Where-Object { $_.Name -in @('body-parser.jsonl', 'es-object-atoms.jsonl') } | ForEach-Object { $_.FullName } | Tee-Object -Append runtime-ci-logs/environment.log
+
+ - name: Build runtime packages
+ run: |
+ pnpm --filter @tempad-dev/shared build
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ pnpm build:mcp
+
+ - name: Test runtime contracts and platform boundaries
+ if: matrix.suite == 'contracts'
+ shell: cmd
+ run: |
+ call pnpm --filter @tempad-dev/mcp exec vitest run --config vitest.config.ts tests/runtime- tests/stable-edge.test.ts --maxWorkers=2 --testTimeout=30000 --hookTimeout=30000 > runtime-ci-logs\unit.log 2>&1
+ set "test_exit=%errorlevel%"
+ type runtime-ci-logs\unit.log
+ exit /b %test_exit%
+
+ - name: Check supervisor recovery and concurrent edges
+ if: matrix.suite == 'prototype'
+ shell: cmd
+ run: |
+ call pnpm mcp:check-runtime-prototype > runtime-ci-logs\prototype.log 2>&1
+ set "test_exit=%errorlevel%"
+ type runtime-ci-logs\prototype.log
+ exit /b %test_exit%
+
+ - name: Check switching, in-flight requests, crashes and rollback
+ if: matrix.suite == 'switch'
+ shell: cmd
+ run: |
+ call pnpm mcp:check-runtime-switch > runtime-ci-logs\switch.log 2>&1
+ set "test_exit=%errorlevel%"
+ type runtime-ci-logs\switch.log
+ exit /b %test_exit%
+
+ - name: Check verified downloads and automatic activation
+ if: matrix.suite == 'updates'
+ shell: cmd
+ run: |
+ call pnpm mcp:check-runtime-updates > runtime-ci-logs\updates.log 2>&1
+ set "test_exit=%errorlevel%"
+ type runtime-ci-logs\updates.log
+ exit /b %test_exit%
+
+ - name: Preserve runtime evidence
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: windows-runtime-node-${{ matrix.node }}-${{ matrix.suite }}
+ path: runtime-ci-logs
+ retention-days: 14
+ if-no-files-found: warn
diff --git a/AGENTS.md b/AGENTS.md
index f52463d0..c161e126 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -99,6 +99,10 @@ default authority unless a routed document says otherwise.
generated output, and include it in the change. `agent-plugin/targets/` is marked as
generated for review; read `agent-plugin/src/` instead. Ordinary `pnpm build` must
not modify agent-plugin artifacts.
+- Installed payload changes (including skills, hooks, manifests, and assets) require a stable
+ version increase in `agent-plugin/src/plugin.json` and regenerated targets. Package-root
+ README and changelog edits alone are exempt. Before pushing, check committed changes with
+ `pnpm agent-plugin:check-version origin/main` against the intended release baseline.
- Keep Codex and Claude development support equivalent. Both manifests must
launch the same working-tree MCP runtime.
- Release MCP configuration must use `@tempad-dev/mcp@latest`, never an alpha
@@ -148,6 +152,7 @@ check is sufficient.
| ------------------------------------------------------- | --------------------------------------------------------------- |
| Test selection, required checks, or troubleshooting | `TESTING.md` |
| Test runtime or coverage architecture | `docs/testing/architecture.md` |
+| Release preparation or compatibility retirement | `docs/releasing.md` |
| Codex desktop IPC discovery, lifecycle, or feedback | `docs/engineering/codex-desktop-ipc.md` |
| End-to-end authoring evolution or live agent evaluation | `docs/testing/agent-authoring-evolution.md` |
| Extension implementation or MCP behavior | `packages/extension/AGENTS.md`, then its routed design document |
diff --git a/README.md b/README.md
index 4392d079..8acbda53 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,7 @@ Both workflows use the same MCP connection to Figma. Compatible clients can inst
### Setup guide
-1. Install Node.js 22.x, 24.x, or 26+ with `npx`. Keep TemPad Dev open in the Figma tab you want the agent to inspect, then enable **Preferences → Agent integration → MCP access**. When prompted, allow the loopback connection to `127.0.0.1`. Canvas authoring is available while MCP access is enabled and the current Figma Design file is editable.
+1. Install Node.js 22.x, 24.x, or 26+ and make sure `npx` is available. Keep TemPad Dev open in the Figma tab you want the agent to inspect, then enable **Preferences → Agent integration → MCP access**. When prompted, allow the loopback connection to `127.0.0.1`. Canvas authoring is available while MCP access is enabled and the current Figma Design file is editable.
2. Select **Set up agents**, choose your client, and follow the displayed path. Dedicated entries include Qwen Code, Qoder, ZCode, Kimi Code, Pi, and Hermes Agent alongside the existing IDE and CLI integrations. Use **Other agents** for another compatible client. The choice only changes the instructions shown; it does not bind or activate an agent.
3. The setup flow uses native marketplace installation for Codex and Claude Code, and the portable Agent Plugin for Cursor and VS Code. Codex App integration uses native IPC without lifecycle hooks. Other entries provide the client's MCP flow plus the two standalone skills, with configuration destinations and documentation links where needed. Pi requires version 0.99 or later with built-in MCP enabled. Every command or config is shown in full for review and copying. These setup paths do not imply native comment delivery or host interruption support for every client.
@@ -104,10 +104,13 @@ For native Codex and Claude marketplace commands and portable Cursor and VS Code
see the [Agent Plugin guide](./agent-plugin/src/README.md).
All plugin and direct `npx`-based setup paths use `@tempad-dev/mcp@latest`.
+Verified MCP releases from 1.0.0 enable background downloads and compatible Hub updates during
+idle MCP sessions. Agent Plugin 1.0.0 and the setup commands select this policy explicitly.
+Existing clients take it up after restarting onto the new release. See
+[runtime update controls](./packages/mcp-server/README.md#runtime-updates-100) for limits and opt-out.
-For this release, use extension **0.23.4**, MCP server **0.11.0**, and Agent
-Plugin **0.5.1** together. See the [upgrade guide](./agent-plugin/src/README.md#upgrading)
-when updating an existing installation.
+See the [upgrade guide](./agent-plugin/src/README.md#upgrading) for minimum versions
+and instructions for updating an existing installation.
Keep TemPad Dev open with MCP enabled while using it. If multiple Figma files are connected, click the MCP badge in the panel for the file you want the agent to inspect; that file becomes the active context. An agent can also list connected sessions and pass an exact `sessionId` to an independent read without changing the active badge.
@@ -123,7 +126,7 @@ When the MCP server is enabled, a badge appears in the TemPad Dev panel title ba
-- **Inactive**: TemPad Dev is connected to a local MCP server, but this tab is not currently active because multiple Figma tabs are open. Click the badge to activate MCP for this tab (this deactivates MCP in other tabs).
+- **Inactive**: TemPad Dev is connected to the local MCP server, but this tab is not the default file. Click its badge to make it the default. Other connected files remain available for reads addressed to their exact session.
@@ -174,7 +177,7 @@ for exact continuation. A single `nodeId` remains supported.
page, or one or more visible nodes in the current selection.
- `upload_asset`: Stores a generated PNG/JPEG/GIF in the local Hub and returns an `assetHash`
for canvas authoring.
-- Binary assets are returned as metadata + HTTP download URLs (`asset.url`) in tool responses. Asset MCP resources are not exposed.
+- Binary assets are returned as metadata with temporary HTTP download URLs (`asset.url`) and, when available, local file paths (`asset.localPath`). Asset MCP resources are not exposed.
---
@@ -341,15 +344,15 @@ Current available plugins:
| Plugin name | Description | Author | Repository |
| -- | -- | -- | -- |
-| `@kong` | Kong Design System | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
-| `@kong/advanced` | Kong Design System (Advanced) | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
-| `@fubukicss/unocss` | UnoCSS by FubukiCSS | [@zouhangwithsweet](https://github.com/@zouhangwithsweet) | [GitHub](https://github.com/zouhangwithsweet/fubukicss-tool) |
-| `@nuxt` | Nuxt UI | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
-| `@nuxt/pro` | Nuxt UI Pro | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
-| `@baidu-health/wz-style` | Custom style for Baidu Health wz-style | [@KangXinzhi](https://github.com/@KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-wz-style) |
-| `@baidu-health/med-style` | Custom style for Baidu Health med-style | [@KangXinzhi](https://github.com/@KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-med-style) |
-| `@tailwind` | CSS to Tailwind CSS | [@haydenull](https://github.com/@haydenull) | [GitHub](https://github.com/haydenull/tempad-dev-plugin-tailwind) |
-| `@react-native` | CSS to React Native StyleSheet | [@CANntyield](https://github.com/@CANntyield) | [GitHub](https://github.com/CANntyield/tempad-dev-plugin-react-native) |
+| `@kong` | Kong Design System | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
+| `@kong/advanced` | Kong Design System (Advanced) | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
+| `@fubukicss/unocss` | UnoCSS by FubukiCSS | [@zouhangwithsweet](https://github.com/zouhangwithsweet) | [GitHub](https://github.com/zouhangwithsweet/fubukicss-tool) |
+| `@nuxt` | Nuxt UI | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
+| `@nuxt/pro` | Nuxt UI Pro | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
+| `@baidu-health/wz-style` | Custom style for Baidu Health wz-style | [@KangXinzhi](https://github.com/KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-wz-style) |
+| `@baidu-health/med-style` | Custom style for Baidu Health med-style | [@KangXinzhi](https://github.com/KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-med-style) |
+| `@tailwind` | CSS to Tailwind CSS | [@haydenull](https://github.com/haydenull) | [GitHub](https://github.com/haydenull/tempad-dev-plugin-tailwind) |
+| `@react-native` | CSS to React Native StyleSheet | [@CANntyield](https://github.com/CANntyield) | [GitHub](https://github.com/CANntyield/tempad-dev-plugin-react-native) |
@@ -367,37 +370,10 @@ If there are components generated by the TemPad Figma plugin on the canvas, TemP
Quirks mode
-> [!CAUTION]
-> Quirks mode is no longer usable as of 2025.04.01. Figma removed the `window.DebuggingHelpers.logSelected` API, which was used to extract style data.
-
-> [!NOTE]
-> New in v0.1.0
-
-New in TemPad Dev v0.1.0, Quirks Mode lets you use the tool even when `window.figma` is unavailable. This mode extracts style data through Figma's debug logs, allowing for basic style generation, albeit with some limitations.
-
-Known missing features generating style codes include:
-
-- Styles added through Effects, corresponding to CSS properties like `box-shadow`, `filter: blur()`, and `backdrop-filter: blur()`.
-- Gradient fill styles. TemPad Dev can only detect the existence of a gradient and outputs it as `linear-gradient()`.
-- Fill styles' blend mode, corresponding to the `background-blend-mode` CSS property.
-- `font-family` of text nodes, which is obtained heuristically and may be inaccurate.
-- Advanced OpenType configurations for text nodes, other than numeric styles, which are generally not used.
-- The ["Scroll Selection into View"](#scroll-selection-into-view) feature is not available in this mode.
-
-Except for the above-mentioned features, others are mostly consistent with the standard mode. If Quirks mode is sufficient for your scenarios, it can eliminate the tedious operation of duplicating to drafts and be used directly in view-only mode. Note that this mode also relies on Figma's globally exposed debug interface and cannot guarantee long-term validity. If Figma removes the related interface again, this mode will also become unavailable.
-
-
-
Compatibility Updates
-
-**2025.04.01**: Figma removed the `window.DebuggingHelpers.logSelected` API, which was used to extract style data. As a result, Quirks mode is no longer usable.
-
-**2024.11.04**: TemPad Dev now managed to bring back the `window.figma` API under view-only mode. But we still cannot guarantee the long-term validity of this feature. If Figma removes the related interface again, this mode will also become unavailable.
-
-**2024.04.08**: TemPad Dev successfully retrieved most style information using currently unblocked debug interfaces, providing a new [Quirks Mode](#quirks-mode). This mode does not rely on `window.figma` but instead parses debug logs to generate style code, with slight differences from the standard mode.
-
-**2024.03.20**: After we posted complaints on the Figma Community Forum, the Figma team stated that they would reinstate the `window.figma` interface in view-only mode in the coming weeks. You can track the progress of this issue on this [thread](https://forum.figma.com/t/figma-removed-window-figma-on-view-only-pages-today/67292).
-
-
+Quirks mode was a fallback that extracted styles from Figma debug logs. It stopped
+working when Figma removed the required API and was removed in TemPad Dev 0.12.0.
+It is unavailable in current releases. See the [extension changelog](./packages/extension/CHANGELOG.md)
+for its history and [Inspect designs](#inspect-designs) for current features.
diff --git a/README.zh-Hans.md b/README.zh-Hans.md
index 50c0efaf..d51104aa 100644
--- a/README.zh-Hans.md
+++ b/README.zh-Hans.md
@@ -101,9 +101,11 @@ Codex 和 Claude 的原生 marketplace 命令,以及 Cursor 和 VS Code 的可
[Agent Plugin 指南](./agent-plugin/src/README.zh-Hans.md)。
所有 plugin 和直接使用 `npx` 的配置路径都使用 `@tempad-dev/mcp@latest`。
+从 MCP 1.0.0 起,经过校验的正式包默认启用后台下载,并在 MCP 会话空闲时自动更新兼容的 Hub。
+Agent Plugin 1.0.0 和安装命令会明确选择此策略;既有客户端需重启到新版本后生效。
+适用范围和关闭方式见[运行时更新控制](./packages/mcp-server/README.zh-Hans.md#运行时更新100)。
-本次版本应配套使用扩展 **0.23.4**、MCP server **0.11.0** 和 Agent Plugin
-**0.5.1**。更新既有安装时,请参阅 [升级指南](./agent-plugin/src/README.zh-Hans.md#升级)。
+最低版本要求和既有安装的更新步骤请参阅[升级指南](./agent-plugin/src/README.zh-Hans.md#升级)。
使用期间请保持 TemPad Dev 打开并启用 MCP。如果连接了多个 Figma 文件,请点击目标文件面板中的 MCP 徽标;该文件会成为 agent 当前访问的上下文。Agent 也可以列出已连接的会话,在独立只读调用中传入精确的 `sessionId`,无需切换激活徽标。
@@ -119,7 +121,7 @@ Codex 和 Claude 的原生 marketplace 命令,以及 Cursor 和 VS Code 的可
-- **Inactive**:TemPad Dev 已连接到本地 MCP 服务器,但由于打开了多个 Figma 标签页,此标签页当前未激活。点击徽标即可为当前标签页激活 MCP(同时会停用其他标签页的 MCP)。
+- **Inactive**:TemPad Dev 已连接到本地 MCP 服务器,但此标签页不是默认文件。点击徽标可将其设为默认文件;其他已连接文件仍可通过精确会话读取。
@@ -164,7 +166,7 @@ Codex 和 Claude 的原生 marketplace 命令,以及 Cursor 和 VS Code 的可
- `get_screenshot`:为每个请求的节点返回有大小限制的渲染 PNG,用于按需视觉验证。
- `get_structure`:精确节点、精确托管页面或当前选中的一个或多个可见节点的结构信息(id、类型、几何数据)。
- `upload_asset`:将生成的 PNG/JPEG/GIF 存入本地 Hub,并返回供画布创作使用的 `assetHash`。
-- 二进制资源会通过工具响应中的元数据 + HTTP 下载地址(`asset.url`)提供;MCP 不再暴露 asset 资源模板。
+- 二进制资源通过工具响应中的元数据提供,包含临时 HTTP 下载地址(`asset.url`),可用时也附带本地文件路径(`asset.localPath`)。MCP 不再暴露 asset 资源模板。
---
@@ -328,15 +330,15 @@ sandboxed extension page 内启动一个全新的 Worker,并在完成或五秒
| 插件名称 | 描述 | 作者 | 仓库 |
| -- | -- | -- | -- |
-| `@kong` | Kong Design System | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
-| `@kong/advanced` | Kong Design System(高级版) | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
-| `@fubukicss/unocss` | FubukiCSS 的 UnoCSS | [@zouhangwithsweet](https://github.com/@zouhangwithsweet) | [GitHub](https://github.com/zouhangwithsweet/fubukicss-tool) |
-| `@nuxt` | Nuxt UI | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
-| `@nuxt/pro` | Nuxt UI Pro | [@Justineo](https://github.com/@Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
-| `@baidu-health/wz-style` | 百度健康 wz-style 自定义样式 | [@KangXinzhi](https://github.com/@KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-wz-style) |
-| `@baidu-health/med-style` | 百度健康 med-style 自定义样式 | [@KangXinzhi](https://github.com/@KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-med-style) |
-| `@tailwind` | CSS 转 Tailwind CSS | [@haydenull](https://github.com/@haydenull) | [GitHub](https://github.com/haydenull/tempad-dev-plugin-tailwind) |
-| `@react-native` | CSS 转 React Native StyleSheet | [@CANntyield](https://github.com/@CANntyield) | [GitHub](https://github.com/CANntyield/tempad-dev-plugin-react-native) |
+| `@kong` | Kong Design System | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
+| `@kong/advanced` | Kong Design System(高级版) | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-kong) |
+| `@fubukicss/unocss` | FubukiCSS 的 UnoCSS | [@zouhangwithsweet](https://github.com/zouhangwithsweet) | [GitHub](https://github.com/zouhangwithsweet/fubukicss-tool) |
+| `@nuxt` | Nuxt UI | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
+| `@nuxt/pro` | Nuxt UI Pro | [@Justineo](https://github.com/Justineo) | [GitHub](https://github.com/Justineo/tempad-dev-plugin-nuxt-ui) |
+| `@baidu-health/wz-style` | 百度健康 wz-style 自定义样式 | [@KangXinzhi](https://github.com/KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-wz-style) |
+| `@baidu-health/med-style` | 百度健康 med-style 自定义样式 | [@KangXinzhi](https://github.com/KangXinzhi) | [GitHub](https://github.com/KangXinzhi/tempad-dev-plugin-med-style) |
+| `@tailwind` | CSS 转 Tailwind CSS | [@haydenull](https://github.com/haydenull) | [GitHub](https://github.com/haydenull/tempad-dev-plugin-tailwind) |
+| `@react-native` | CSS 转 React Native StyleSheet | [@CANntyield](https://github.com/CANntyield) | [GitHub](https://github.com/CANntyield/tempad-dev-plugin-react-native) |
@@ -354,37 +356,9 @@ sandboxed extension page 内启动一个全新的 Worker,并在完成或五秒
-
-**2025.04.01**:Figma 移除了用于提取样式数据的 `window.DebuggingHelpers.logSelected` API,Quirks 模式因此不可用。
-
-**2024.11.04**:TemPad Dev 成功在只读模式下重新获取了 `window.figma` API,但仍无法保证该能力的长期有效性。如果 Figma 再次移除相关接口,该模式也将不可用。
-
-**2024.04.08**:TemPad Dev 通过当前尚未被封禁的调试接口,成功获取了大部分样式信息,并引入了新的 [Quirks 模式](#quirks-mode)。该模式不依赖 `window.figma`,而是通过解析调试日志生成样式代码,与标准模式存在少量差异。
-
-**2024.03.20**:在我们向 Figma 社区论坛提交反馈后,Figma 团队表示将在未来几周内恢复只读模式下的 `window.figma` 接口。你可以在这个 [讨论帖](https://forum.figma.com/t/figma-removed-window-figma-on-view-only-pages-today/67292) 中跟踪该问题的进展。
-
-
+Quirks 模式曾通过 Figma 调试日志提取样式。所需 API 被 Figma 移除后,该模式失效,
+并在 TemPad Dev 0.12.0 中被删除,当前版本已不可用。历史记录见
+[扩展更新日志](./packages/extension/CHANGELOG.md),当前功能见[检查设计](#检查设计)。
diff --git a/TESTING.md b/TESTING.md
index de4a73fc..0de9275d 100644
--- a/TESTING.md
+++ b/TESTING.md
@@ -29,6 +29,10 @@ Then run:
Use this sequence as the default pre-PR verification. Also run `pnpm test:coverage` when changing
pure utilities or coverage configuration, as detailed below. CI enforces the coverage floor.
+Push a validation branch under `ci/` to run the full Linux build and Windows runtime matrices
+without publishing `main`. Validation branches compare plugin payload versions against
+`origin/main`; ordinary main pushes retain their previous-commit baseline.
+
## Command reference
Root:
@@ -42,6 +46,70 @@ Root:
Hub-to-extension, extension-to-Hub, and formatted MCP fixture responses.
- `pnpm agent-plugin:check-installer` (networked discovery smoke test with `plugins@1.3.4`;
run after `pnpm agent-plugin:build`, without installing into a host)
+- `pnpm mcp:check-profiles` (builds and launches isolated real CLI/Hub processes; checks
+ shared release ownership, default and overridden directories, actual release/checkout WebSocket
+ ports, and independent shutdown). CI runs the same harness beside the bridge check.
+ Windows process-level isolation is unverified: the harness refuses Windows and Unix hook
+ fixtures are skipped there. Unit tests cover pipe naming by user and checkout and the unchanged
+ release pipe; runtime-directory overrides cannot isolate two Windows Hubs for the same checkout.
+- `pnpm mcp:check-host-context` (builds and exercises real CLI processes against a private fixture
+ Hub; checks both host launch orders, sanitized child environments, registration after reconnect,
+ and continued bridging after the original launcher exits). A real MCP SDK fixture also checks
+ that the pre-context Hub contract accepts new CLIs through initialization, identity, and tools.
+ CI runs it alongside profile isolation.
+ It refuses Windows for the same fixed-pipe constraint; it does not connect to installed hosts.
+ Native adapter and real Hub socket tests separately cover routing and persisted host contexts.
+- `pnpm mcp:check-runtime-prototype` builds a bundled-only prototype in a private runtime root,
+ using ephemeral WebSocket/HTTP ports and a synthetic extension. It checks two initialized
+ edges, supervisor crash recovery without replacing the Hub or replaying a write, admission
+ fencing, Stop, host-context isolation, legacy hook routing, and last-client shutdown. It
+ reports startup/recovery timings and process RSS. The installed CLI and live runtimes are
+ unchanged; this harness does not download or switch versions. Windows uses named pipes and
+ checks actual directory and pipe ACLs rather than Unix permission bits.
+- `pnpm mcp:check-runtime-switch` builds trusted local A/B fixtures from source in an ignored
+ directory, then exercises manual switching with two initialized SDK clients and a synthetic
+ extension. It checks draining, fixed asset endpoints, a disconnected write and late receipt,
+ crashes at all four journal phases, port conflicts, rollback retaining a later Stop, and
+ pending Claude Stop notices surviving both switching and rollback with durable hook retries.
+ New edges also attach during a delayed handoff and after real stale writer leases expire;
+ the fixture's initialization deadline exceeds the edge's 90-second recovery bound.
+ `TEMPAD_SWITCH_CASES` selects comma-separated cases for diagnosis; `TEMPAD_KEEP_PROTOTYPE=1`
+ retains isolated evidence. It never downloads or updates installed runtimes. CI runs the full
+ matrix on Linux and Windows with Node 22 and 24; native host behavior remains outside this harness.
+- Before runtime archive checks, run `pnpm mcp:prepare-release-dependencies` with network access.
+ It verifies and caches the exact frozen production dependency graph without package scripts;
+ use the same pnpm cache for the subsequent offline packaging step. A workspace install alone
+ may not cache all verification evidence required by the reduced production lockfile.
+- `pnpm mcp:check-runtime-updates` builds complete release archives from the frozen local store,
+ serves them from an isolated loopback registry, and exercises default release enrollment, staging and explicit
+ activation through real CLI/Hub processes. It checks mixed policies sharing one Hub, exact
+ pins, newest-compatible selection below the stable tag, tag rollback without downgrade,
+ exact-version requests without substitution, frozen presentation, incompatible epochs,
+ offline operation, corrupt caches, and zero
+ registry requests from checkout builds. A real metadata write failure must leave the supervisor
+ and MCP service alive without starting acquisition. After all owners exit, concurrent installed-release
+ launches upgrade both control and Hub across an epoch boundary without registry access or
+ allowing an older CLI to downgrade them. A separate simulated Codex namespace checks idle
+ automatic activation, generic MCP clients, verified Claude hooks, unverified Claude/off-policy deferral, continuous tool work, bounded request
+ waiting and measured admission pause. This is process evidence, not new native acceptance. CI runs this on
+ Node 22/24 on Linux and Windows; native host acceptance remains outside this fixture. No installed
+ runtime or public registry is used by the acquisition test.
+- `pnpm mcp:prepare-release --output --verify-baseline` builds and verifies
+ the publishable dependency closure and checks the current stable npm baseline. See
+ [release policy](docs/releasing.md#runtime-release-policy) before refreshing the review hash.
+- `pnpm mcp:measure-runtime --output --runs 5 [--lockfile ] [...]`
+ compares trusted, explicitly supplied local release artifacts in the given version order. It
+ performs MCP initialization/catalog reads through fresh CLI/Hub processes and records artifact
+ hashes, structural comparisons across release windows, startup/attach timing and RSS. It never
+ downloads packages or connects to installed hosts. TCP listeners are remapped to ephemeral
+ loopback ports and IPC listeners are confined to a temporary root; Windows is refused.
+ Preserve the resolved dependency lock alongside the report. Structural equality does not prove
+ guidance or behavior compatibility, and warm attachment is not uninterrupted session recovery.
+- `pnpm mcp:measure-hooks --hook --output --runs 30` measures an inspected
+ hook against an isolated synthetic receiver, including slow commit, lost reply, failed commit,
+ disconnect and exit before request arrival. It does not modify installed hooks or contact the
+ user's Hub. File/directory fsync and injected delay are not full-device flush, disk-pressure,
+ or installed-host adoption evidence. Keep measurement reports outside version control.
- `pnpm --filter @tempad-dev/extension test:setup` (install extension browser runtime)
- `pnpm --filter @tempad-dev/extension test:node` (extension node tests only)
- `pnpm --filter @tempad-dev/extension test:browser` (extension browser tests only)
@@ -54,6 +122,64 @@ Root:
- `pnpm agent-eval:log ` (retain the small amount
of provenance needed to trust a live authoring run)
+### Windows runtime CI
+
+The [Windows runtime workflow](.github/workflows/windows-runtime.yml) runs the runtime unit
+tests and all three supervisor/switch/acquisition process harnesses on Node 22 and 24. It
+preserves LF source bytes for the reviewed manifest hash, asserts the selected Node version,
+and uploads per-step logs. Push a validation branch under `ci/` to run it without publishing
+`main`; it also runs for `main`, pull requests and manual dispatch.
+
+All Windows matrix jobs must pass on the candidate before release. These checks exercise real
+named pipes, ACLs, child processes, downloads, concurrent edges, in-flight requests, automatic
+activation, crashes and rollback. They do not establish installed Codex/Claude desktop behavior,
+and do not change the Windows limitations of the separate profile/host-context/bridge harnesses.
+
+### Native runtime switch acceptance
+
+Use the [authoring runbook](docs/testing/agent-authoring-evolution.md) for installed-host
+replacement, preflight and native calls. The following opt-in fixture tests the packaged
+release path without fetching or publishing a release. It requires macOS/Linux, a short new
+private directory, and unused release discovery ports; it never stops another port owner.
+
+```sh
+pnpm mcp:prepare-release-dependencies
+node packages/mcp-server/scripts/native-runtime-fixture.mjs --root /tmp/tp-native-example --prepare
+node packages/mcp-server/scripts/native-runtime-fixture.mjs --root /tmp/tp-native-example --serve --hold-candidate
+# In another terminal, use the canonical descriptor path printed by --prepare:
+pnpm agent-plugin:build --runtime-fixture /private/tmp/tp-native-example/runtime-fixture.json
+pnpm build:ext
+```
+
+Preparation deploys frozen dependencies offline and verifies complete immutable A/B archives.
+The fixture changes only package identity and the compiled registry origin to a fixed loopback
+server; normal discovery, bootstrap, staging, consent and handoff logic remain in use. Both
+packages are private and point publication at the fixture registry. Never publish these archives.
+The ignored dev plugin routes Codex, Claude and its hooks to the same private release-test
+namespace. It does not change any generated release target or the default checkout configuration.
+Use a release-profile extension for this fixture, not a checkout-profile build.
+
+After replacing the plugin and reloading the extension, require preflight and a native MCP read.
+`--hold-candidate` keeps the registry on the initial version while this baseline is established.
+Send `SIGUSR2` to the registry PID printed by `--serve` to expose the already-verified candidate;
+this does not restart either the host or registry. Invoke `--check-runtime-update` with the
+descriptor's environment to discover it immediately instead of waiting for the scheduled check.
+An off-policy observer can hold the candidate staged for inspection; close it before testing
+activation. Enrolled, initialized, wait-capable MCP clients can activate after five idle seconds.
+Record stable host/edge identities, changed Hub identity, native feedback/lifecycle/Stop and
+subsequent canvas work. Claude requires a verified session and receipt/retry-capable hooks;
+unverified sessions and legacy hooks must remain staged. The explicit update command remains
+available, but cannot override an attached off-policy observer or other compatibility blockers.
+The registry request log records only local acquisition. This establishes installed-host behavior
+for the local candidate; official-registry provenance and cross-version release compatibility
+remain separate checks. Do not count synthetic SDK clients as native-host acceptance.
+
+When finished, uninstall the fixture plugin and confirm its CLI/Hub/supervisor processes have
+stopped before shutting down the registry. Regenerate the ordinary plugin with
+`pnpm agent-plugin:build`, rebuild the extension with `TEMPAD_MCP_DEV_CHECKOUT="$PWD"`, and follow
+the same installation/preflight workflow to restore checkout development. Retain evidence outside
+version control. Never reuse a fixture root for different source bytes.
+
Per package:
- `pnpm --filter @tempad-dev/extension test:run` (includes Worker dependency checks and the real
diff --git a/agent-plugin/src/CHANGELOG.md b/agent-plugin/src/CHANGELOG.md
index 433117c6..f144f70a 100644
--- a/agent-plugin/src/CHANGELOG.md
+++ b/agent-plugin/src/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 1.0.0
+
+- Enabled verified runtime downloads and compatible Hub updates between operations on macOS,
+ Linux and Windows with MCP 1.0.0. Claude requires a verified session and current lifecycle
+ hooks; development checkouts keep their bundled runtime.
+- Retried interrupted Claude lifecycle messages without repeating their effects, including
+ during runtime switches. Preserved Unicode text and tolerated malformed Hub replies.
+- Kept development Claude hooks connected to their own checkout while a release runtime is running.
+- Clarified recovery after failed or uncertain canvas writes, native text binding limits,
+ supported text entities, and browser compatibility for token-based CSS. Unsupported entity
+ warnings require extension 1.0.0 or later.
+- Corrected component sizing, asset storage, shadow inspection, and Chinese setup guidance.
+
## 0.5.1
- Fixed Claude session binding so Figma Stop reaches the correct conversation.
diff --git a/agent-plugin/src/README.md b/agent-plugin/src/README.md
index b6703c10..031c96e0 100644
--- a/agent-plugin/src/README.md
+++ b/agent-plugin/src/README.md
@@ -76,7 +76,8 @@ skill installations should also remove the old skill after installing the new on
The standalone package retains the former name as a generated alias for one extension store
release cycle so setup commands in older installed extensions continue to work.
-Use TemPad Dev extension **0.23.4 or later** and MCP server **0.11.0 or later**.
+For the complete 1.0 release, update the TemPad Dev extension, MCP server, and
+Agent Plugin (or its standalone skills) to **1.0.0 or later**.
Node.js **22.x, 24.x, or 26+** is required for the MCP server.
Multi-node reads and consumer-aware token resolution require matching protocol 15 Hub and
extension builds. MCP 0.11.0 also supports extension 0.22.0 through protocol 13, preserving
@@ -84,13 +85,17 @@ existing reads, sessions, tasks, and canvas writes during the store rollout. New
prompt an extension upgrade; results identify older selection and token semantics.
MCP 0.10.0 and later use `manage_design_task` in place of the older lifecycle tools.
-1. Update the browser extension to 0.23.4 or later and reload the Figma tab. Version 0.23.4 includes packaged rewrite rules and SVG placement fixes; multi-node reads arrived in 0.23.0.
+1. Update the browser extension to 1.0.0 or later and reload the Figma tab.
2. Update the installed plugin through the client or installer used originally. With standalone
setup, update both `figma-design-to-code` and `design-in-figma`.
3. Keep the release MCP configuration on `@tempad-dev/mcp@latest`; replace any previous
- `@alpha` or fixed alpha version. Reconnect the MCP client and start a new task so it loads the
- updated tools and skills. If a stale Hub is reported, close tasks using the old MCP server
- before reconnecting.
+ `@alpha` or fixed alpha version. Plugin 1.0.0 enables verified runtime downloads with MCP
+ 1.0.0 or later on macOS, Linux and Windows. Compatible Hub updates switch automatically during idle MCP
+ sessions with waiting support; Claude also requires a verified session and receipt/retry-capable hooks.
+ When upgrading from an unmanaged Hub, disconnect all TemPad MCP
+ clients once, then restart their MCP servers. Edge processes, installed plugins and incompatible
+ runtime changes still require their normal update/restart. See the
+ [runtime update controls](https://github.com/ecomfe/tempad-dev/blob/main/packages/mcp-server/README.md#runtime-updates-100).
4. Open TemPad Dev, enable **MCP access**, and click the MCP badge in the intended Figma tab when
a session choice is needed. The badge selects the default file; agents can also discover
connected sessions and target independent reads by exact session ID.
diff --git a/agent-plugin/src/README.zh-Hans.md b/agent-plugin/src/README.zh-Hans.md
index 95a300f7..b8ddafe9 100644
--- a/agent-plugin/src/README.zh-Hans.md
+++ b/agent-plugin/src/README.zh-Hans.md
@@ -66,6 +66,9 @@ claude plugin install tempad-dev@tempad-dev
设计任务的状态栏显示 agent 状态、Stop/Done 和评论入口。Stop 会立即阻止当前任务继续写入,
在执行中的操作结束后永久取消该任务;重新连接也不会恢复它。后续设计工作需要明确开始新任务。
+正常配置只需完成 TemPad Dev 扩展设置并安装本插件,无需另行配置控制地址、
+环境变量或辅助服务。
+
评论目前仅支持通过兼容 Codex App 的原生会话通道发送。Queue 将整批评论交给宿主的原生队列,
等待会话可以执行时再处理。宿主确认接收后,TemPad Dev 会清空本批评论和标记、停止发送指示,
并允许继续输入下一批;这表示已接收,不表示 agent 已完成修改。如果原生入队不可用,
@@ -89,7 +92,10 @@ Codex App 的任务绑定和状态同步使用 MCP 元数据及原生 IPC,不
宿主不可用时,本地取消仍然生效。Claude 保留生命周期和 Stop 通知 hooks;Claude、Codex CLI
等尚未接入原生投递的客户端提供任务状态和 Stop/Done,但不显示评论入口,已有草稿不会删除。
Claude 会在下一个启用 hook 的工具边界收到 Stop 通知。任务暂停可用新的 lease epoch 恢复,
-恢复后必须先重新读取画布才能写入;取消的任务不能恢复。详见
+恢复后必须先重新读取画布才能写入;取消的任务不能恢复。Agent 会遵守 Stop,不会自动创建
+替代任务;必要或用户要求的后续设计工作可以显式开始新任务,无需另行在 Figma 中解锁。
+只有实际持有目标会话的宿主确认支持时,才会启用 Codex 原生投递,无需手动配置连接。
+当前验证范围详见
[任务与客户端设计](https://github.com/ecomfe/tempad-dev/blob/main/docs/extension/mcp-design-tasks.md)。
原生适配器在 macOS/Linux 上使用 Unix socket,在 Windows 上使用 Codex 的本机 Named Pipe。
@@ -97,6 +103,9 @@ Claude 会在下一个启用 hook 的工具边界收到 Stop 通知。任务暂
自动执行和完整的已安装插件/Figma UI 流程仍待实测;Windows/Linux 的证据限于源码检查和
自动化测试,尚不能据此宣称完整宿主支持。
+Agent 会在对话中报告结果并附上 Figma 链接,用户可以继续提出后续要求。
+任务工具返回文本和结构化数据。
+
## 升级
设计 skill 已更名为 `design-in-figma`,与展示名 **Design in Figma** 一致。
@@ -104,19 +113,23 @@ Claude 会在下一个启用 hook 的工具边界收到 Stop 通知。任务暂
安装新版后还应移除旧名称的 skill。
独立 skill 包会保留旧名称的生成兼容入口一个扩展商店发布周期,让已安装旧扩展中的配置命令继续有效。
-请使用 TemPad Dev 扩展 **0.23.4 或更高版本**和 MCP server **0.11.0 或更高版本**。
+如需使用完整的 1.0 发布,请将 TemPad Dev 扩展、MCP server、Agent Plugin(或其独立 skill)
+均更新到 **1.0.0 或更高版本**。
MCP server 要求 Node.js **22.x、24.x 或 26+**。
多节点读取和按消费节点解析 token 要求 Hub 与扩展均使用匹配的 protocol 15 构建。
MCP 0.11.0 同时通过 protocol 13 兼容扩展 0.22.0,在商店更新期间保留既有读取、会话、任务和画布写入。
新版读取选项会提示升级扩展,响应会明确标注旧版选区和 token 语义。
MCP 0.10.0 及之后版本使用 `manage_design_task` 代替旧的生命周期工具。
-1. 将浏览器扩展更新到 0.23.4 或更高版本并重新加载 Figma 标签页。0.23.4 包含内置 rewrite 规则和 SVG 定位修复;多节点读取从 0.23.0 开始提供。
+1. 将浏览器扩展更新到 1.0.0 或更高版本并重新加载 Figma 标签页。
2. 通过原先使用的客户端或安装器更新 plugin。独立配置时,请同时更新
`figma-design-to-code` 和 `design-in-figma`。
3. 正式版 MCP 配置使用 `@tempad-dev/mcp@latest`;请替换旧的 `@alpha` 或固定 alpha 版本。
- 重新连接 MCP client 并新建任务,以加载更新后的工具和 skill。若提示 Hub 过期,请先关闭
- 使用旧 MCP server 的任务,再重新连接。
+ Plugin 1.0.0 配合 MCP 1.0.0 或更高版本,会在 macOS、Linux 和 Windows 上启用经过校验的后台下载,
+ 在支持切换等待的 MCP 会话空闲时自动切换兼容的 Hub。Claude 还要求已验证的会话和支持回执与重试的 hook。
+ 从未启用更新的 Hub 升级时,需先断开所有 TemPad MCP client,
+ 再重启其 MCP server,一次即可。入口进程、已安装插件和不兼容的运行时改动仍需按原方式更新或重启。
+ 详见[运行时更新控制](https://github.com/ecomfe/tempad-dev/blob/main/packages/mcp-server/README.zh-Hans.md#运行时更新100)。
4. 打开 TemPad Dev 并启用 **MCP access**;需要选择会话时,点击目标 Figma 标签页内的 MCP
badge。该 badge 选择默认文件;agent 也可以发现已连接会话,并通过精确 session ID
指定独立读取的目标。
diff --git a/agent-plugin/src/clients/claude/hooks.json b/agent-plugin/src/clients/claude/hooks.json
index 173f8eb1..6571c919 100644
--- a/agent-plugin/src/clients/claude/hooks.json
+++ b/agent-plugin/src/clients/claude/hooks.json
@@ -1,5 +1,5 @@
{
- "description": "Bind Figma tasks, collect user feedback at host lifecycle boundaries, and honor task Stop.",
+ "description": "Bind Figma tasks, report host lifecycle, and deliver task Stop notices.",
"hooks": {
"Stop": [
{
diff --git a/agent-plugin/src/clients/shared/lifecycle.mjs b/agent-plugin/src/clients/shared/lifecycle.mjs
index 6327b2f9..c5af9ed9 100644
--- a/agent-plugin/src/clients/shared/lifecycle.mjs
+++ b/agent-plugin/src/clients/shared/lifecycle.mjs
@@ -1,19 +1,28 @@
// Installed hooks exchange lifecycle identity and Stop notices with the existing Hub. They never
// launch an agent, poll a model, change host trust, or unlock a stopped Figma file.
import { randomUUID } from 'node:crypto'
+import { readFileSync } from 'node:fs'
import { connect } from 'node:net'
-import { join } from 'node:path'
+import { dirname, isAbsolute, join, resolve } from 'node:path'
import process from 'node:process'
import { clearTimeout, setTimeout } from 'node:timers'
+import { pathToFileURL } from 'node:url'
import { assertTrustedSocket, resolveDefaultRoot } from './runtime-paths.mjs'
+import {
+ checkoutProfile,
+ resolveRuntimeProfile,
+ runtimeDirectories,
+ runtimeSocketPath
+} from './runtime-profile.mjs'
let socket
const deadline = setTimeout(() => process.exit(0), 750)
try {
let text = ''
+ process.stdin.setEncoding('utf8')
for await (const chunk of process.stdin) {
- text += String(chunk)
+ text += chunk
if (text.length > 1_000_000) process.exit(0)
}
const input = JSON.parse(text)
@@ -54,60 +63,128 @@ try {
kind: process.argv[2] === 'claude' ? 'claude' : 'codex',
sessionId: input.session_id
}
- const runtimeDir = process.env.TEMPAD_MCP_RUNTIME_DIR || join(resolveDefaultRoot(), 'run')
- const path =
- process.platform === 'win32' ? '\\\\.\\pipe\\tempad-mcp' : join(runtimeDir, 'mcp.sock')
- assertTrustedSocket(path)
- socket = connect(path)
- const pending = new Map()
- let sequence = 0
- let buffer = ''
- socket.on('data', (data) => {
- buffer += data.toString()
- if (buffer.length > 1_000_000) {
- socket.destroy()
- return
- }
- let index
- while ((index = buffer.indexOf('\n')) >= 0) {
- const line = buffer.slice(0, index)
- buffer = buffer.slice(index + 1)
- let message
- try {
- message = JSON.parse(line)
- } catch {
- continue
- }
- const entry = pending.get(message.id)
- if (!entry) continue
- pending.delete(message.id)
- if (message.error) entry.reject(new Error('The Hub does not support this hook request.'))
- else entry.resolve(message.result)
+ let profile = { kind: 'release' }
+ let path
+ if (process.argv[3] === '--runtime-profile') {
+ const descriptorPath = process.argv[4]
+ const descriptor = JSON.parse(readFileSync(descriptorPath, 'utf8'))
+ if (typeof descriptor.serverName !== 'string' || !descriptor.serverName)
+ throw new Error('Missing checkout MCP server name.')
+ // Read the same generated configuration as MCP/preflight so directory overrides cannot drift.
+ const pluginRoot = resolve(dirname(descriptorPath), '../..')
+ const config = JSON.parse(readFileSync(join(pluginRoot, '.mcp.json'), 'utf8')).mcpServers?.[
+ descriptor.serverName
+ ]
+ if (
+ typeof config?.env?.TEMPAD_MCP_DEV_CHECKOUT !== 'string' ||
+ typeof config?.args?.[0] !== 'string'
+ )
+ throw new Error('Missing checkout MCP configuration.')
+ const runtimeDir = config.env.TEMPAD_MCP_RUNTIME_DIR
+ if (
+ runtimeDir !== undefined &&
+ (typeof runtimeDir !== 'string' || (runtimeDir && !isAbsolute(runtimeDir)))
+ )
+ throw new Error('Checkout runtime directory must be an absolute path.')
+ const env = {
+ ...process.env,
+ TEMPAD_MCP_DEV_CHECKOUT: config.env.TEMPAD_MCP_DEV_CHECKOUT,
+ ...(runtimeDir !== undefined ? { TEMPAD_MCP_RUNTIME_DIR: runtimeDir } : {})
}
- })
- const disconnected = () => {
- for (const entry of pending.values()) entry.reject(new Error('The Hub hook connection closed.'))
- pending.clear()
+ profile = resolveRuntimeProfile(env, pathToFileURL(resolve(pluginRoot, config.args[0])))
+ if (
+ profile.kind !== 'checkout' ||
+ profile.id !== descriptor.id ||
+ checkoutProfile(descriptor.checkout).id !== profile.id
+ )
+ throw new Error('Invalid checkout hook profile.')
+ path = runtimeSocketPath(runtimeDirectories(env, profile).runtime, profile)
+ } else {
+ const runtimeDir = process.env.TEMPAD_MCP_RUNTIME_DIR || join(resolveDefaultRoot(), 'run')
+ path = process.platform === 'win32' ? '\\\\.\\pipe\\tempad-mcp' : join(runtimeDir, 'mcp.sock')
}
- socket.on('error', disconnected)
- socket.on('close', disconnected)
- await new Promise((resolve, reject) => {
- socket.once('connect', resolve)
- socket.once('error', reject)
- })
- const request = (method, params) =>
- new Promise((resolve, reject) => {
- const id = ++sequence
- pending.set(id, { resolve, reject })
- socket.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n')
- })
- const result = await request('tempad/client-hook', {
+ assertTrustedSocket(path)
+ const lifecycle = {
...identity,
event,
invocationId: randomUUID(),
...(taskId ? { taskId } : {}),
...(typeof input.turn_id === 'string' ? { turnId: input.turn_id } : {})
- })
+ }
+ const params = { ...lifecycle, version: 2, profile, issuedAt: Date.now() }
+ let legacy = false
+ async function request() {
+ socket = connect(path).setEncoding('utf8')
+ const connection = socket
+ let timer
+ try {
+ return await new Promise((resolve, reject) => {
+ timer = setTimeout(() => reject(new Error('Hook reply timed out.')), 200)
+ let buffer = ''
+ socket.on('data', (data) => {
+ buffer += data
+ if (buffer.length > 1_000_000) {
+ socket.destroy()
+ return
+ }
+ let index
+ while ((index = buffer.indexOf('\n')) >= 0) {
+ const line = buffer.slice(0, index)
+ buffer = buffer.slice(index + 1)
+ let message
+ try {
+ message = JSON.parse(line)
+ } catch {
+ continue
+ }
+ if (message?.id !== 1) continue
+ if (message.error)
+ reject(
+ Object.assign(new Error('The Hub does not support this hook request.'), {
+ code: message.error.code
+ })
+ )
+ else resolve(message.result)
+ return
+ }
+ })
+ socket.on('error', reject)
+ socket.once('close', () => reject(new Error('The Hub hook connection closed.')))
+ socket.once('connect', () => {
+ socket.write(
+ JSON.stringify({
+ jsonrpc: '2.0',
+ id: 1,
+ method: 'tempad/client-hook',
+ params: legacy ? lifecycle : params
+ }) + '\n'
+ )
+ })
+ })
+ } finally {
+ clearTimeout(timer)
+ connection.destroy()
+ }
+ }
+ let result
+ for (let attempt = 0; attempt < 4; attempt++) {
+ try {
+ result = await request()
+ if (legacy) break
+ const receipt = result?.receipt
+ if (receipt?.version !== 2 || receipt.invocationId !== params.invocationId)
+ throw new Error('Invalid lifecycle receipt.')
+ if (receipt.state === 'applied') break
+ if (receipt.state !== 'accepted') throw new Error('Invalid lifecycle receipt state.')
+ result = undefined
+ } catch (error) {
+ result = undefined
+ // Only a definite old-schema rejection permits fallback. A lost reply
+ // retries the same v2 invocation, never a second lifecycle effect.
+ if (error?.code === -32602 && !legacy) legacy = true
+ }
+ await new Promise((resolve) => setTimeout(resolve, 30))
+ }
// Old Hubs may still offer comment batches; never emit or acknowledge those.
if (
!result?.receiptId &&
diff --git a/agent-plugin/src/clients/shared/runtime-profile.d.mts b/agent-plugin/src/clients/shared/runtime-profile.d.mts
new file mode 100644
index 00000000..e732bf4e
--- /dev/null
+++ b/agent-plugin/src/clients/shared/runtime-profile.d.mts
@@ -0,0 +1,17 @@
+type RuntimeProfile = { kind: 'release' } | { kind: 'checkout'; id: string }
+export function checkoutProfile(checkout: string): Extract
+export function resolveRuntimeProfile(
+ env?: NodeJS.ProcessEnv,
+ entryUrl?: string | URL
+): RuntimeProfile
+export function runtimeDirectories(
+ env?: NodeJS.ProcessEnv,
+ profile?: RuntimeProfile,
+ options?: { tmpdir?: string; homedir?: string; uid?: number; prepare?: boolean }
+): { root: string | undefined; runtime: string; log: string; assets: string; state: string }
+export function runtimeSocketPath(
+ runtime: string,
+ profile?: RuntimeProfile,
+ platform?: NodeJS.Platform,
+ home?: string
+): string
diff --git a/agent-plugin/src/clients/shared/runtime-profile.mjs b/agent-plugin/src/clients/shared/runtime-profile.mjs
new file mode 100644
index 00000000..c966707f
--- /dev/null
+++ b/agent-plugin/src/clients/shared/runtime-profile.mjs
@@ -0,0 +1,104 @@
+import { Buffer } from 'node:buffer'
+import { createHash } from 'node:crypto'
+import { realpathSync, statSync } from 'node:fs'
+import { homedir, tmpdir } from 'node:os'
+import { dirname, isAbsolute, join } from 'node:path'
+import process from 'node:process'
+import { fileURLToPath } from 'node:url'
+
+import { prepareDefaultRoot, resolveDefaultRoot } from './runtime-paths.mjs'
+
+function hash(value) {
+ return createHash('sha256').update(value).digest('hex').slice(0, 16)
+}
+
+export function checkoutProfile(checkout) {
+ if (!isAbsolute(checkout)) throw new Error('TEMPAD_MCP_DEV_CHECKOUT must be an absolute path.')
+ const root = realpathSync(checkout)
+ if (!statSync(root).isDirectory()) throw new Error('The checkout must be a directory.')
+ return { kind: 'checkout', id: hash(root) }
+}
+
+export function resolveRuntimeProfile(env = process.env, entryUrl) {
+ if (env.TEMPAD_MCP_DEV_CHECKOUT === undefined) return { kind: 'release' }
+ const profile = checkoutProfile(env.TEMPAD_MCP_DEV_CHECKOUT)
+ if (entryUrl) {
+ const root = realpathSync(env.TEMPAD_MCP_DEV_CHECKOUT)
+ const dist = join(root, 'packages/mcp-server/dist')
+ for (const name of ['cli.mjs', 'hub.mjs']) {
+ const path = join(dist, name)
+ try {
+ if (realpathSync(path) === path && statSync(path).isFile()) continue
+ } catch (cause) {
+ throw new Error(`Missing checkout runtime: ${path}. Run pnpm build:mcp in the checkout.`, {
+ cause
+ })
+ }
+ throw new Error(`Invalid checkout runtime: ${path}. Build the checkout before starting MCP.`)
+ }
+ if (dirname(realpathSync(fileURLToPath(entryUrl))) !== dist)
+ throw new Error('The MCP entry point does not belong to TEMPAD_MCP_DEV_CHECKOUT.')
+ }
+ return profile
+}
+
+export function runtimeDirectories(
+ env = process.env,
+ profile = resolveRuntimeProfile(env),
+ options = {}
+) {
+ const temporary = options.tmpdir ?? tmpdir()
+ const home = options.homedir ?? homedir()
+ const user = options.uid ?? process.getuid?.() ?? hash(home)
+ const checkout = profile.kind === 'checkout'
+ const needsRoot =
+ !env.TEMPAD_MCP_RUNTIME_DIR || !env.TEMPAD_MCP_LOG_DIR || !env.TEMPAD_MCP_ASSET_DIR
+ let root
+ if (needsRoot) {
+ root = checkout
+ ? join(temporary, `tpd-${user}-${profile.id}`)
+ : (options.prepare ? prepareDefaultRoot : resolveDefaultRoot)(temporary)
+ }
+ // Explicit directories remain usable for isolated tests, but never merge checkout and release.
+ const directory = (override, name) => {
+ if (!override) return join(root, name)
+ return checkout ? join(override, `checkout-${profile.id}`) : override
+ }
+ const runtime = directory(env.TEMPAD_MCP_RUNTIME_DIR, 'run')
+ let state
+ if (!checkout && env.TEMPAD_MCP_STATE_DIR !== undefined) {
+ state = env.TEMPAD_MCP_STATE_DIR
+ } else if (env.TEMPAD_MCP_STATE_DIR) {
+ state = directory(env.TEMPAD_MCP_STATE_DIR, 'state')
+ } else if (env.TEMPAD_MCP_RUNTIME_DIR) {
+ state = join(runtime, 'state')
+ } else {
+ state = checkout
+ ? join(home, '.tempad-dev', 'checkouts', profile.id, 'state')
+ : join(home, '.tempad-dev', 'state')
+ }
+ return {
+ root,
+ runtime,
+ log: directory(env.TEMPAD_MCP_LOG_DIR, 'log'),
+ assets: directory(env.TEMPAD_MCP_ASSET_DIR, 'assets'),
+ state
+ }
+}
+
+export function runtimeSocketPath(
+ runtime,
+ profile = { kind: 'release' },
+ platform = process.platform,
+ home = homedir()
+) {
+ if (platform !== 'win32') {
+ const socket = join(runtime, 'mcp.sock')
+ if (profile.kind === 'checkout' && Buffer.byteLength(socket) > 103)
+ throw new Error('Checkout socket path is too long. Choose a shorter TEMPAD_MCP_RUNTIME_DIR.')
+ return socket
+ }
+ return profile.kind === 'checkout'
+ ? `\\\\.\\pipe\\tempad-mcp-${hash(home)}-${profile.id}`
+ : '\\\\.\\pipe\\tempad-mcp'
+}
diff --git a/agent-plugin/src/mcp.json b/agent-plugin/src/mcp.json
index 4326590e..63a42b75 100644
--- a/agent-plugin/src/mcp.json
+++ b/agent-plugin/src/mcp.json
@@ -4,7 +4,7 @@
"tempad-dev": {
"type": "stdio",
"command": "npx",
- "args": ["-y", "@tempad-dev/mcp@latest"]
+ "args": ["-y", "@tempad-dev/mcp@latest", "--runtime-updates=stable"]
}
}
}
diff --git a/agent-plugin/src/plugin.json b/agent-plugin/src/plugin.json
index 98ec9e1a..20bca1fa 100644
--- a/agent-plugin/src/plugin.json
+++ b/agent-plugin/src/plugin.json
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "tempad-dev",
- "version": "0.5.1",
+ "version": "1.0.0",
"description": "Connect your coding agent to Figma. Create and edit native designs, inspect existing designs, and implement UI in your codebase.",
"author": {
"name": "TemPad Dev"
diff --git a/agent-plugin/src/skills/design-in-figma/SKILL.md b/agent-plugin/src/skills/design-in-figma/SKILL.md
index 9278d7b3..28d6ac55 100644
--- a/agent-plugin/src/skills/design-in-figma/SKILL.md
+++ b/agent-plugin/src/skills/design-in-figma/SKILL.md
@@ -134,7 +134,9 @@ screen or section boundaries and carry shared roles coherently across them.
Inspect every `apply_canvas` result, including warnings. Repair each observed
unintended defect or disclose why it remains. A local validation failure calls
for a local payload correction; it does not justify discarding a working root
-or simplifying away the intended content. Open pixels again after the final
+or simplifying away the intended content. If a write times out, loses its result,
+or cannot verify rollback, follow [local recovery](references/editing.md#recover-locally)
+before another write. Open pixels again after the final
material write, covering every materially distinct screen. Verify native facts
with `get_structure` when identity, placement, editability, or representation
matters. Opened pixels prove visual access, not good judgment; a structural pass
diff --git a/agent-plugin/src/skills/design-in-figma/references/canvas-html.md b/agent-plugin/src/skills/design-in-figma/references/canvas-html.md
index ef11c260..a8428ac3 100644
--- a/agent-plugin/src/skills/design-in-figma/references/canvas-html.md
+++ b/agent-plugin/src/skills/design-in-figma/references/canvas-html.md
@@ -68,10 +68,14 @@ reveals issues one at a time.
unlinks that field.
- A `span` contains only text and ` ` or ` ` line breaks. Use
`whitespace-pre-wrap` for literal newlines or repeated spaces. A plain `&` is
- literal unless it forms a semicolon-terminated entity; supported entities
- decode. Canvas typography does not inherit from a parent `div`: put font and
- other text utilities on each `span`/TEXT node. Put flex/grid, gaps, padding,
- borders, corners, and box shadows on a parent `div`.
+ literal unless it forms a semicolon-terminated entity. Numeric decimal/hex
+ entities and these named entities decode: `amp`, `apos`, `bull`, `copy`, `gt`,
+ `hellip`, `ldquo`, `lsquo`, `lt`, `mdash`, `middot`, `nbsp`, `ndash`, `quot`,
+ `rdquo`, `reg`, `rsquo`, `trade`. Other names remain literal; extension 1.0.0+
+ also reports an `unknown-html-entity` warning. Use the Unicode character or a
+ numeric entity instead. Canvas typography does not inherit from
+ a parent `div`: put font and other text utilities on each `span`/TEXT node.
+ Put flex/grid, gaps, padding, borders, corners, and box shadows on a parent `div`.
- A component tag is childless, includes its returned `data-ref`, and accepts
returned props plus the shared class, identity, variable, and style
attributes.
diff --git a/agent-plugin/src/skills/design-in-figma/references/component-authoring.md b/agent-plugin/src/skills/design-in-figma/references/component-authoring.md
index 99573887..b747048e 100644
--- a/agent-plugin/src/skills/design-in-figma/references/component-authoring.md
+++ b/agent-plugin/src/skills/design-in-figma/references/component-authoring.md
@@ -241,7 +241,7 @@ the call returns the set as `rootNodeId`, this creates Default and Hover:
```json
{
"mode": "create",
- "markup": "
",
+ "markup": "
",
"native": {
"screen/default": {
"component": { "id": "ComponentSetID:created-button-set" }
diff --git a/agent-plugin/src/skills/design-in-figma/references/editing.md b/agent-plugin/src/skills/design-in-figma/references/editing.md
index f8653bd5..f020d91e 100644
--- a/agent-plugin/src/skills/design-in-figma/references/editing.md
+++ b/agent-plugin/src/skills/design-in-figma/references/editing.md
@@ -32,10 +32,30 @@ native references needed for the intended change.
## Recover locally
Read the entire mutation result. For a rejected payload, correct all reported
-issues together without changing the design to fit the error. A verification
-failure is rolled back by TemPad; do not assume a partial successful edit.
-For an unknown transport outcome, read the exact target before retrying a create
-or removal so an uncertain response does not become a duplicate mutation.
+issues together without changing the design to fit the error. An execution failure
+requires inspecting its reported outcome before retrying. TemPad attempts rollback
+after a failed write. Only an explicit no-write outcome (`CANVAS_APPLY_ABORTED` or
+`EXTENSION_DELIVERY_FAILED`) skips file readback. Other diagnostics can originate
+inside a mutation, especially with older extensions. Follow the returned readback
+requirement: rollback checks cover only part of native state.
+Automatic Undo can be skipped when this attempt has no confirmed remaining change.
+Do not suggest manual Undo in that case: it could remove an earlier successful edit.
+
+A timeout, lost result, or unverified rollback leaves the canvas outcome uncertain.
+Do not replay the write. Wait for a pending operation to settle; reload a stuck
+runtime only when the tool calls for it. Then read the affected surviving node with
+`get_structure` or `get_code`, carrying the current task and epoch. If the node was
+removed, use `get_structure` with its exact page ID. An empty page snapshot counts;
+empty selection, failed-node-only results, screenshots, and resource catalogs do not
+satisfy readback. Inspect the returned state before deciding on a repair or retry.
+If the failed request also changed local variables or styles, inspect those affected
+resources with `get_design_system`; a canvas read alone does not verify their state.
+If it cannot establish a safe next change, ask the user to inspect the canvas.
+
+Readback is also required after task resume or reconnection. A file's unknown-write
+requirement survives Hub restarts, Stop, and a fresh task; none of those replaces the
+read. A notice that readback is still required means the last read did not establish
+usable canvas state. Stop cancellation remains in force independently of readback.
Repair warnings where they occur. Replace a whole root only when an observed
structural defect requires it and the complete intended content can be
diff --git a/agent-plugin/src/skills/design-in-figma/references/paints-effects.md b/agent-plugin/src/skills/design-in-figma/references/paints-effects.md
index c32b82f2..2ce41f9d 100644
--- a/agent-plugin/src/skills/design-in-figma/references/paints-effects.md
+++ b/agent-plugin/src/skills/design-in-figma/references/paints-effects.md
@@ -35,9 +35,11 @@ Named scales such as `shadow-md` are theme references, not portable geometry:
Never assume Tailwind defaults or create a token only to resolve a named class.
`shadow-none`, `inset-shadow-none`, and `text-shadow-none` explicitly clear.
-Treat an outer shadow's rendered halo as part of the composition. Inspect the
-final PNG beyond the root edges; visible granular or noisy fringe, or a halo
-that dominates the captured bounds, is a defect even when the frame itself is
+Treat an outer shadow's rendered halo as part of the composition. Exact-node
+screenshots may crop to the node's bounds; inspect an enclosing composition with
+enough surrounding space to judge the halo. Its absence from a cropped PNG is
+not evidence that it is absent on canvas. Visible granular or noisy fringe, or a
+halo that dominates the composition, is a defect even when the frame itself is
intact. Preserve intended depth by tightening blur, spread, or opacity or using
smaller layered shadows, then recheck. Do not flatten established material
treatment merely to hide the defect.
diff --git a/agent-plugin/src/skills/design-in-figma/references/rich-text.md b/agent-plugin/src/skills/design-in-figma/references/rich-text.md
index 3e26cc97..3ac85e5e 100644
--- a/agent-plugin/src/skills/design-in-figma/references/rich-text.md
+++ b/agent-plugin/src/skills/design-in-figma/references/rich-text.md
@@ -38,6 +38,22 @@ Use `{ "ref": "s1" }` for a catalog range style and `{ "ref": "v1" }` for a
range variable. `null` unlinks supported styles or hyperlinks; omission
preserves.
+Numeric font size, line height, and letter spacing can also affect a `\n`
+immediately after a range. Give adjacent ranges explicit, consistent ownership.
+If an error identifies a retained Text style at that boundary, keep the earlier
+range unchanged or explicitly replace the affected style only when that is the
+intended design; expanding a range can overwrite the next range's formatting.
+Do not retry an unchanged request that reports this native limitation.
+
+Variable `null` is verified after application. Paragraph indent and spacing apply
+to the affected paragraphs, even when the requested range covers only part of
+their text. Figma may retain a paragraph alias after unlinking; TemPad tries to
+preserve its current literal value without changing the next paragraph. If no
+safe fallback exists, including on an empty paragraph, it reports failure rather
+than claiming the binding was cleared. Follow the returned readback requirement
+and inspect affected text and resources before another write. Then preserve the
+binding or explicitly replace the affected paragraph as the intended design requires.
+
Hyperlinks support URLs and node targets. For a same-result target:
```json
diff --git a/agent-plugin/src/skills/figma-design-to-code/references/assets-and-tokens.md b/agent-plugin/src/skills/figma-design-to-code/references/assets-and-tokens.md
index c42d134b..9df81ff8 100644
--- a/agent-plugin/src/skills/figma-design-to-code/references/assets-and-tokens.md
+++ b/agent-plugin/src/skills/figma-design-to-code/references/assets-and-tokens.md
@@ -8,11 +8,13 @@ code, including references whose token metadata is missing.
Follow the project's established asset and icon policy before TemPad delivery
details.
-- Download bytes only from a TemPad-provided `asset.url`. Never substitute a
- public internet asset.
+- Use the returned `asset.localPath` when accessible; otherwise download
+ `asset.url`. These are temporary Hub delivery locations: copy needed files
+ into the project's normal asset location. Never substitute a public internet
+ asset.
- Treat assets as files to store or reference, not text evidence to parse.
- If project policy forbids storing them, reference TemPad URLs only when the
- user accepts the local-server dependency, and report it.
+ user accepts the temporary local-server dependency, and report it.
- Treat emitted `