diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f2f9471..3f08547 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -2,14 +2,41 @@ "name": "cavalry-collective", "owner": { "name": "Cavalry Collective", - "email": "adam@cavalry.sg" + "email": "adam@cavalry.sg", + "url": "https://cavalry.sg" }, - "description": "Visual Stack — interactive wireframing and UI review with your coding agent.", + "description": "Visual Stack adds a Figma-like feedback layer to AI coding agents. Comment directly on a wireframe or a running app, and get the next revision in the same place.", "plugins": [ { "name": "vstack", + "displayName": "Visual Stack", "source": "./plugins/vstack", - "description": "Visual Stack is interactive wireframing for Claude Code. Build a screen from a prompt, screenshot, reference site, or design system, then comment directly on the result and receive each revision in the same browser workspace. It can also review a running app or public website route by route. Wireframes are self-contained HTML; local review state stays in the project." + "description": "Stop prompting. Start pointing. Visual Stack adds a Figma-like feedback layer to Claude Code. Create a new screen from a prompt, screenshot, reference site, or design system, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Claude publishes the next revision into the same workspace. Compare revisions on a timeline, preview desktop, tablet, and mobile layouts, and keep every comment attached to the element, route, and version it refers to. Wireframes are self-contained HTML and review state stays in your project.", + "author": { + "name": "Cavalry Collective", + "email": "adam@cavalry.sg", + "url": "https://cavalry.sg" + }, + "homepage": "https://www.cavalry.sg/visual-stack", + "repository": "https://github.com/Cavalry-Collective/visual-stack", + "license": "MIT", + "category": "design", + "keywords": [ + "wireframe", + "wireframing", + "mockup", + "prototype", + "ui", + "ui-review", + "ux", + "design", + "design-review", + "design-feedback", + "frontend", + "annotate", + "visual-feedback", + "screenshot" + ] } ] } diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..d1f2ecc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,72 @@ +name: Bug report +description: Something in a skill or workspace does not work as described. +labels: [bug] +body: + - type: markdown + attributes: + value: | + Do not report a security problem here. Follow + [SECURITY.md](https://github.com/Cavalry-Collective/visual-stack/blob/main/SECURITY.md) + instead. + + - type: textarea + id: what-happened + attributes: + label: What happened + description: What you did, what you expected, and what you got instead. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: > + Start from the command you ran. A bug we can reproduce is a bug we can + fix. + placeholder: | + 1. Run `/vstack:review Wireframe a login screen.` + 2. Click the submit button and leave a comment. + 3. Send the comment. + validations: + required: true + + - type: dropdown + id: host + attributes: + label: Host + options: + - Claude Code + - Codex + - Grok + - Other + validations: + required: true + + - type: input + id: version + attributes: + label: Visual Stack version + description: > + The version shown in the plugin manager, or the commit you installed + from. + placeholder: 4.7.0 + validations: + required: true + + - type: input + id: environment + attributes: + label: Operating system, Node version, and browser + placeholder: macOS 15.2, Node 22.11.0, Chrome 131 + validations: + required: true + + - type: textarea + id: output + attributes: + label: Server output or console errors + description: > + Anything the review server printed, or errors from the browser console. + This is pasted as code, so no backticks are needed. + render: text diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..774f8ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Report a security issue + url: https://github.com/Cavalry-Collective/visual-stack/blob/main/SECURITY.md + about: Follow the security policy. Do not open a public issue. + - name: Something you would rather not post publicly + url: mailto:adam@cavalry.sg + about: Email the maintainer directly. diff --git a/.github/ISSUE_TEMPLATE/idea.yml b/.github/ISSUE_TEMPLATE/idea.yml new file mode 100644 index 0000000..6f32dfd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/idea.yml @@ -0,0 +1,40 @@ +name: Idea or feature request +description: Something Visual Stack should do that it does not do yet. +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: The problem + description: > + What you were trying to do, and where the current tools got in the way. + Lead with the problem rather than the solution. + validations: + required: true + + - type: textarea + id: idea + attributes: + label: What you would like instead + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Which part of Visual Stack + options: + - review (wireframes) + - review (running app or website) + - user-story-map + - The workspace UI + - Host support + - Something else + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: What you tried instead + description: Workarounds you already have, and why they fall short. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..d485f7f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +## What this changes + + + +## How it was tested + + + +## Checklist + +- [ ] Installed the plugin from this branch and drove the affected skill end to end. +- [ ] Nothing writes outside the user's project, and nothing transmits anywhere, unless that is the skill's stated purpose. +- [ ] Per-machine state resolves through `lib/workdir.mjs` and lands under `.vstack/local//`. +- [ ] Pages stay self-contained. No external requests at runtime. +- [ ] Edited `lib/shell/` rather than a stamped region, ran `node plugins/vstack/lib/build-shell.mjs stamp`, and committed both. +- [ ] Added or renamed a plugin, and updated `.claude-plugin/marketplace.json` in the same commit. +- [ ] Renamed a tool, and added its former directory name to the `LEGACY` map in `lib/workdir.mjs`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b0e0e35 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + tests: + name: Tests (Node ${{ matrix.node }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: ['18', '22'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Review lifecycle + run: node plugins/vstack/skills/review/tests/review-lifecycle.mjs + - name: Host profiles + run: node plugins/vstack/skills/review/tests/host-profiles.mjs + - name: Working-directory resolution + run: node plugins/vstack/skills/review/tests/workdir.mjs + + shell: + name: Stamped shell is current + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + # Fails when a page's stamped region has drifted from lib/shell/. + # Fix by running `node plugins/vstack/lib/build-shell.mjs stamp`. + - name: Check for drift + run: node plugins/vstack/lib/build-shell.mjs check + + manifests: + name: Manifests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Install Claude Code + run: npm install --global @anthropic-ai/claude-code + # The community-marketplace review pipeline runs this same check on every + # submission, so a warning here is a warning a reviewer would see. + - name: Validate the marketplace + run: claude plugin validate . --strict + - name: Validate the plugin + run: claude plugin validate ./plugins/vstack --strict + # Both hosts read a version out of their own manifest, so they can drift + # apart silently and ship the same commit under two version numbers. + - name: Host manifests declare the same version + run: | + node -e ' + const { readFileSync } = require("node:fs") + const version = file => JSON.parse(readFileSync(file, "utf8")).version + const claude = version("plugins/vstack/.claude-plugin/plugin.json") + const codex = version("plugins/vstack/.codex-plugin/plugin.json") + if (claude !== codex) { + console.error(`Claude manifest says ${claude}, Codex manifest says ${codex}.`) + process.exit(1) + } + console.log(`Both host manifests declare ${claude}.`) + ' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..59b6607 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +jobs: + version-matches-tag: + name: Tag matches the manifest version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # An explicit version in plugin.json is what Claude Code compares against + # to decide an update exists. Tagging a release without bumping it leaves + # every installed copy believing it is already current. + - name: Compare the tag against plugin.json + run: | + node -e ' + const { readFileSync } = require("node:fs") + const tag = process.env.GITHUB_REF_NAME.replace(/^v/, "") + const declared = JSON.parse( + readFileSync("plugins/vstack/.claude-plugin/plugin.json", "utf8") + ).version + if (declared !== tag) { + console.error(`Tag ${process.env.GITHUB_REF_NAME} does not match plugin.json version ${declared}.`) + console.error("Bump the version in both host manifests, commit, then move the tag.") + process.exit(1) + } + console.log(`Tag and manifest agree on ${declared}.`) + ' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d26e2e9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,123 @@ +# Changelog + +What changed in each release of Visual Stack, newest first. Versions follow +[semantic versioning](https://semver.org). Full notes for each release are on +the [releases page](https://github.com/Cavalry-Collective/visual-stack/releases). + +The version in `plugins/vstack/.claude-plugin/plugin.json` is what your host +compares against to decide an update is available. See the release checklist in +[`CONTRIBUTING.md`](CONTRIBUTING.md). + +## 4.8.0 — 2026-08-04 + +- **The plugin declares a version.** Until now it shipped without one, so every + commit to `main` counted as a release. Your host now compares version numbers + and updates when this number changes. A copy installed before this release has + no version on record and keeps comparing commits until it updates once. +- **A comment lands on the element it was left on, not a lookalike.** The + selector recorded for a comment was cut off after eight steps, and a short + path can first-match a different element elsewhere on the page, which is the + one `querySelector` returns. The recorded selector is now the shortest one + that matches a single element, and a path still ambiguous at full length is + rooted at `body` so the chain is exact. +- **Reanchoring prefers the element carrying the comment's words.** When a + selector still parses but its match shares nothing with what was captured, an + element scoring on text or identity is now taken ahead of it. +- Both host manifests carry the full set of distribution metadata: version, + display name, homepage, repository, license, and keywords covering what + someone would search for. Descriptions across all three manifests are drawn + from the README. +- CI runs the tests on Node 18 and 22, checks the stamped shell for drift, and + validates both manifests with the same tool the community-marketplace review + pipeline runs. A release workflow rejects a tag that disagrees with the + manifest version. +- Issue and pull request templates, a changelog, and a release procedure in + `CONTRIBUTING.md`. + +## 4.7.0 — 2026-08-04 + +- A question the agent asks is carried onto the next version and drawn in the + comments list, with a reply box already open. Carried questions were counted + everywhere but had no card, so there was no way to answer or dismiss one. +- A carried card says which version raised it, as `from r3` in a live app review + and `from v3` in a wireframe review. +- The walk over earlier versions sorts explicitly, rather than relying on + integer-like object keys happening to iterate in order. + +## 4.6.0 — 2026-08-04 + +- **The wireframe tool is now `/vstack:review`.** `/vstack:wireframe` still works + as a thin alias. Rounds already under `.vstack/local/wireframe/` keep working, + and nothing is migrated behind your back. +- **Three ways a comment could go missing, fixed.** A save no longer deletes + comments it did not mention, a reply lands in the version the workspace has + open, and a question survives the next publication. +- The project-planning tools (spec, start, phase-build, phase-preview, and the + `go` alias) move to `experimental/`, where no host discovers them. `review` and + `user-story-map` are what ships. +- Comments anchor to what you clicked on a page that scrolls its own window. +- Addressed comments read green across the mark, the card, and the composer. +- One live-link client and one heartbeat protocol shared by the review server + and the JSON bridge. + +## 4.5.0 — 2026-08-03 + +- Machine state and pipeline state split first, tool name second. Everything + per-machine lives under `.vstack/local//`, gitignored by one line. + `pipeline.json`, `specs/`, and `build/` stay tracked. +- The JSON bridge takes `--tool`, so the skills that share it get their own + directories. + +## 4.4.0 — 2026-08-03 + +- The tools stop scattering dot-directories. `.ui-review//` and + `.vstack-bridge/` move under `.vstack/`. +- **Breaking:** reviews in flight under `.ui-review/`, and comment drafts under + the `ui-review:*` `localStorage` keys, are orphaned. There is no migration. + Finish or discard any open review before updating. + +## 4.3.0 — 2026-08-03 + +- **Codex host.** Install from the Codex marketplace and invoke `$wireframe` to + build or review a UI in the annotation workspace. +- `hosts/codex.json` carries Codex's labels, capabilities, and update commands. + Skills still depend only on the contract, never on a particular agent product. +- Clearing a review opens a confirm dialog that says how many comments go. +- The comments panel becomes its own full-width view below 720px. +- Merging server state no longer undoes a local dismissal, reply, or reopen that + is still inside the autosave window. + +## 4.2.0 — 2026-08-01 + +- **Shared shell.** One top bar, palette, theme, and scrubber for every tool page. +- **Live wireframe review.** Point the workspace at a localhost app or a public + site and it proxies the real screens, so comments land on them. +- **`/vstack:phase-preview`** (was `phase-wireframe`). +- Each local server checks once whether the plugin has moved on, and shows one + dismissable line when it has. Opt out with `VSTACK_NO_UPDATE_CHECK=1`. + +## 4.1.0 — 2026-07-30 + +- **Visual Stack.** A name, a mark, and three new skills: `/vstack:go`, + `/vstack:spec`, and `/vstack:phase-build`. +- `/vstack:init` becomes `/vstack:start`, and now also sets up a design-only + workspace or connects an existing codebase without touching it. + +## 2.0.0 — 2026-07-28 + +- **Breaking:** `/cavalry:ui-review` becomes `/cavalry:wireframe`. +- Adds `init` and `phase-wireframe`. +- Approve and cancel from the workspace. Publishing a shareable link publishes + the design rather than the review workspace. + +## 1.1.0 — 2026-07-27 + +- Adds the **ui-review** skill: annotate on the page itself, with severity, + threaded replies, viewport switching, and a version timeline. +- Both skills get a live link back to the agent session, so feedback returns + without copy-paste. + +## 1.0.0 — 2026-07-27 + +- First release, shipping the **user-story-map** skill: journey activities as + columns, release phases as rows, stories as drag-and-drop cards. diff --git a/CLAUDE.md b/CLAUDE.md index ef40035..4c1dc2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,6 +38,36 @@ node plugins/vstack/lib/build-shell.mjs stamp # write lib/shell/ into every p node plugins/vstack/lib/build-shell.mjs check # exit 1 if any page has drifted ``` +The manifests are validated by the same tool the community-marketplace review +pipeline runs: + +```bash +claude plugin validate . --strict # .claude-plugin/marketplace.json +claude plugin validate ./plugins/vstack --strict # the plugin manifest +``` + +`.github/workflows/ci.yml` runs all of the above on every pull request. + +CI cannot install the plugin, so rehearse that locally before a release. +`CLAUDE_CONFIG_DIR` keeps it out of the real config: without it, a local-path +marketplace is written to user settings and shadows the published +`cavalry-collective` until it is removed. The source must be `./`, not `.`. + +```bash +SANDBOX=$(mktemp -d) +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin marketplace add ./ +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin install vstack@cavalry-collective +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin details vstack # what a user sees +rm -rf $SANDBOX +``` + +Nothing above runs a review end to end. For that, load the plugin from disk and +drive the skill in a real project: + +```bash +claude --plugin-dir ./plugins/vstack +``` + ## Architecture ### Contracts / engine / adapters / profiles @@ -121,6 +151,88 @@ purpose so no host discovers them as installable skills; their pages still carry the stamped shell and are kept from drifting by `build-shell.mjs`. Moving one back under `skills/` is the whole act of re-releasing it. +## Distribution and releases + +This repo is what a stranger installs, so its public metadata is part of the +product. `claude plugin validate --strict` must pass on both manifests before +any change ships, because the community-marketplace review pipeline runs the +same check. + +### Two host manifests, one identity + +`plugins/vstack/.claude-plugin/plugin.json` and +`plugins/vstack/.codex-plugin/plugin.json` describe the same plugin to two +hosts, and `.claude-plugin/marketplace.json` repeats the Claude entry. + +- Change one manifest, change all three in the same commit. +- `version`, `author`, `homepage`, `repository`, `license`, and `keywords` are + identical across them. Only the description's host name and the Codex + `interface` block differ. +- Descriptions take their wording from `README.md`. The README is where the + product's voice is decided; a manifest quotes it rather than inventing a + second one. +- Keywords cover what someone would type to find this, not what it is built + from. Do not add a keyword the description cannot back up. +- CI fails when the two host manifests declare different versions. + +### Versioning + +`version` is declared, so it is what a host compares against to decide an update +exists. **Pushing commits without bumping it ships nothing to anyone.** + +- Bump `version` in both host manifests, and add the release to `CHANGELOG.md`, + in the release commit. +- Tag `vX.Y.Z` on the commit that lands on `main`. + `.github/workflows/release.yml` fails when the tag and the manifest disagree. +- MAJOR for a breaking change to a skill name, an on-disk path, or a protocol. + MINOR for new behaviour. PATCH for a fix. +- Orphaning a user's in-flight state is MAJOR, and it needs a `LEGACY` entry in + `lib/workdir.mjs` rather than a migration. +- `lib/update-check.mjs` mirrors the host's own update decision. It reads the + declared `version` first and falls back to the install SHA only for a copy + installed before a version existed. Changing how the version is declared means + changing that file. + +### Cutting a release when asked + +When the user says to cut, ship, or publish a release, run this end to end. The +`main` ruleset requires a pull request, so nothing lands directly on `main`. + +1. **Decide the version.** Read the commits since the last tag, apply the semver + rule above, and tell the user the number you picked and why in one line. + Proceed on that number. Stop and ask only when the same set of commits reads + as either MINOR or MAJOR depending on how a breaking change is judged. +2. **Verify before proposing anything.** Run the tests, the shell check, both + validate commands, and the install rehearsal from *Commands*. A failure here + ends the release. Report it and fix it first. +3. **Branch.** `release/vX.Y.Z` off current `main`. +4. **Bump and record.** `version` in both host manifests, and a `CHANGELOG.md` + entry written from the merged commits, newest first, with breaking changes + called out. +5. **Open the PR.** Title `vX.Y.Z — `. The body is + the changelog entry, so it can be reused as the release notes. +6. **Watch CI.** `gh pr checks --watch`. Every check must pass. A red + check means fix it on the branch and watch again, never merge past it. +7. **Merge when green.** Squash. The user has standing approval for this merge + and for the tag and release that follow, so do not ask again for a release + they asked for. +8. **Tag `main`.** Pull the squashed commit, tag it `vX.Y.Z`, and push the tag. + The release workflow re-checks the tag against the manifest. +9. **Publish the GitHub release** with the changelog entry as its notes, then + give the user the release URL. + +Stop and report rather than working around a problem: a red check that is not +yours to fix, a ruleset that rejects the merge, or a tag that already exists. + +Nothing here is a dry run. Every step from 5 onward is public. + +### Contributor-facing files + +`CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, and this file state the +same rules to three audiences. A rule is owned by one of them and referenced +from the others. When a rule here changes and a contributor has to follow it, +update the PR checklist in the same commit. + ## Coding standards Keep cross-cutting concerns in shared modules, and keep `lib/` pure. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ccdbe3c..de78968 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,6 +14,46 @@ Visual Stack is a Claude Code and Codex plugin: skills, prompts, and the HTML wo Install the plugin from your branch and drive the skill end to end in a real project. A skill that has only been read is untested. For Codex changes, validate the plugin manifest and review skill, then run `node plugins/vstack/skills/review/tests/host-profiles.mjs`. +CI runs on every PR and repeats what you can run locally: + +```bash +node plugins/vstack/skills/review/tests/review-lifecycle.mjs +node plugins/vstack/skills/review/tests/host-profiles.mjs +node plugins/vstack/skills/review/tests/workdir.mjs +node plugins/vstack/lib/build-shell.mjs check +claude plugin validate . --strict +claude plugin validate ./plugins/vstack --strict +``` + +Nothing runs end to end in CI, so a green build is not a tested skill. + +CI also cannot install the plugin. Rehearse that locally before a release, with `CLAUDE_CONFIG_DIR` pointed at a throwaway directory so the local-path marketplace is not written to your real settings, where it would shadow the published `cavalry-collective`: + +```bash +SANDBOX=$(mktemp -d) +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin marketplace add ./ +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin install vstack@cavalry-collective +CLAUDE_CONFIG_DIR=$SANDBOX/.claude claude plugin details vstack +rm -rf $SANDBOX +``` + +`details` prints the name, version, description and skill inventory a user sees. The source must be `./` and not `.`. + +## Cutting a release + +Both host manifests declare a `version`, and that version is what Claude Code and Codex compare against to decide an update exists. **Pushing commits without bumping it ships nothing to anyone.** + +1. Branch `release/vX.Y.Z` off `main`. +2. Bump `version` to the same value in `plugins/vstack/.claude-plugin/plugin.json` and `plugins/vstack/.codex-plugin/plugin.json`. +3. Add the release to `CHANGELOG.md`, newest first. +4. Open a PR. `main` takes no direct pushes, and a release is not an exception. +5. Merge when CI is green, then tag `vX.Y.Z` on the squashed commit on `main`. The release workflow fails when the tag and the manifest disagree. +6. Publish the GitHub release with the changelog entry as its notes. + +Version to semantic versioning: MAJOR for a breaking change to a skill name, an on-disk path, or a protocol; MINOR for new behaviour; PATCH for a fix. + +Orphaning a user's in-flight state is a MAJOR change, and it needs a `LEGACY` entry in `lib/workdir.mjs` rather than a migration. + ## Reporting problems - A bug or an unclear skill: open an issue. diff --git a/plugins/vstack/.claude-plugin/plugin.json b/plugins/vstack/.claude-plugin/plugin.json index 87c48dd..b661c82 100644 --- a/plugins/vstack/.claude-plugin/plugin.json +++ b/plugins/vstack/.claude-plugin/plugin.json @@ -1,8 +1,30 @@ { "name": "vstack", - "description": "Visual Stack is interactive wireframing for Claude Code. Build a screen from a prompt, screenshot, reference site, or design system; comment directly on the result; and receive each revision in the same browser workspace. It can also review a running app or public website route by route, turning feedback on your own app into source-code changes. Wireframes are self-contained HTML and local review state stays in the project.", + "displayName": "Visual Stack", + "version": "4.8.0", + "description": "Stop prompting. Start pointing. Visual Stack adds a Figma-like feedback layer to Claude Code. Create a new screen from a prompt, screenshot, reference site, or design system, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Claude publishes the next revision into the same workspace. Compare revisions on a timeline, preview desktop, tablet, and mobile layouts, and keep every comment attached to the element, route, and version it refers to. Wireframes are self-contained HTML and review state stays in your project.", "author": { "name": "Cavalry Collective", - "email": "adam@cavalry.sg" - } + "email": "adam@cavalry.sg", + "url": "https://cavalry.sg" + }, + "homepage": "https://www.cavalry.sg/visual-stack", + "repository": "https://github.com/Cavalry-Collective/visual-stack", + "license": "MIT", + "keywords": [ + "wireframe", + "wireframing", + "mockup", + "prototype", + "ui", + "ui-review", + "ux", + "design", + "design-review", + "design-feedback", + "frontend", + "annotate", + "visual-feedback", + "screenshot" + ] } diff --git a/plugins/vstack/.codex-plugin/plugin.json b/plugins/vstack/.codex-plugin/plugin.json index 3b89641..be21990 100644 --- a/plugins/vstack/.codex-plugin/plugin.json +++ b/plugins/vstack/.codex-plugin/plugin.json @@ -1,24 +1,40 @@ { "name": "vstack", - "description": "Visual Stack is the interactive wireframe and UI review tool for Codex.", + "version": "4.8.0", + "description": "Stop prompting. Start pointing. Visual Stack adds a Figma-like feedback layer to Codex. Create a new screen from a prompt, screenshot, reference site, or design system, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Codex publishes the next revision into the same workspace. Compare revisions on a timeline, preview desktop, tablet, and mobile layouts, and keep every comment attached to the element, route, and version it refers to. Wireframes are self-contained HTML and review state stays in your project.", "author": { "name": "Cavalry Collective", "email": "adam@cavalry.sg", "url": "https://cavalry.sg" }, - "homepage": "https://github.com/Cavalry-Collective/visual-stack", + "homepage": "https://www.cavalry.sg/visual-stack", "repository": "https://github.com/Cavalry-Collective/visual-stack", "license": "MIT", - "keywords": ["wireframe", "ui", "design", "review", "prototype"], + "keywords": [ + "wireframe", + "wireframing", + "mockup", + "prototype", + "ui", + "ui-review", + "ux", + "design", + "design-review", + "design-feedback", + "frontend", + "annotate", + "visual-feedback", + "screenshot" + ], "skills": "./skills/", "interface": { "displayName": "Visual Stack", - "shortDescription": "Wireframe directly with Codex", - "longDescription": "Build a wireframe or review a running UI, comment directly on the screen, and receive each Codex revision in one linked workspace.", + "shortDescription": "Stop prompting. Start pointing.", + "longDescription": "Visual Stack adds a Figma-like feedback layer to Codex. Create a new screen, or open an app you already have running. Click any element and leave feedback exactly where the problem is hiding, and Codex publishes the next revision into the same workspace.", "developerName": "Cavalry Collective", "category": "Productivity", "capabilities": ["Interactive", "Write"], - "websiteURL": "https://github.com/Cavalry-Collective/visual-stack", + "websiteURL": "https://www.cavalry.sg/visual-stack", "defaultPrompt": [ "Build a wireframe for this screen and open it for review.", "Open the running app in an interactive UI review workspace." diff --git a/plugins/vstack/lib/update-check.mjs b/plugins/vstack/lib/update-check.mjs index a960b93..2cd9788 100644 --- a/plugins/vstack/lib/update-check.mjs +++ b/plugins/vstack/lib/update-check.mjs @@ -9,13 +9,16 @@ * dismissable line under the bar. * * WHAT COUNTS AS NEWER - * This plugin ships without a `version` in plugin.json, which is Claude Code's - * way of saying "every commit is a release": it then keys its own update - * decision on the git commit the plugin was installed from, and records that in - * ~/.claude/plugins/installed_plugins.json. So this asks the same question the - * same way — the SHA behind the installed copy, against the head of the default - * branch. Should a `version` ever come back, it wins, because that is what - * Claude Code would key on instead. + * This asks the question Claude Code itself would ask, so the banner never + * disagrees with what `/plugin update` would do. Claude Code keys its update + * decision on the plugin's `version` when plugin.json declares one, and on the + * git commit the plugin was installed from when it does not, recording either + * in ~/.claude/plugins/installed_plugins.json. + * + * plugin.json declares a version, so the comparison is normally version against + * version. A copy installed before that version existed has no version on + * record, and falls back to the SHA behind it against the head of the default + * branch. * * A working copy is not an install. Running from a clone (developing the plugin * itself) finds no entry, and the check returns nothing rather than telling you diff --git a/plugins/vstack/skills/review/assets/workspace.html b/plugins/vstack/skills/review/assets/workspace.html index 8598430..8966dff 100644 --- a/plugins/vstack/skills/review/assets/workspace.html +++ b/plugins/vstack/skills/review/assets/workspace.html @@ -2705,9 +2705,16 @@

const sibs = n.parentElement ? [...n.parentElement.children].filter(c => c.tagName === n.tagName) : []; if (sibs.length > 1) part += `:nth-of-type(${sibs.indexOf(n) + 1})`; parts.unshift(part); - if (parts.length >= 8) break; } - return parts.join(' > '); + /* A short suffix of the path can first-match a lookalike elsewhere in the + document, and querySelector would hand back that one. Use the shortest + suffix that matches only this element; a path that is ambiguous even at + full length gets rooted at body, where the child chain is exact. */ + for (let i = parts.length - 1; i >= 0; i--) { + const sel = parts.slice(i).join(' > '); + try { if (fdoc.querySelectorAll(sel).length === 1) return sel } catch {} + } + return (parts[0]?.startsWith('#') ? '' : 'body > ') + parts.join(' > '); } /** The part of the page this sits in — a dialog, a nav, a card. What a person would say first when telling someone else where to look. */ @@ -2863,6 +2870,11 @@

} } if (bestScore >= 3) return keep(best); + /* Reaching here, the selector's hit matched none of the captured words or + identity — an ambiguous path from an older version can land on a lookalike + first. An element that carries the words is better evidence than a path + that merely still parses. */ + if (bestScore >= 2) return keep(best); if (hit) return keep(hit); missCache.set(a.id, domEpoch); return null;