Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87c9c3a151 | |||
| ddd6fd96b8 | |||
| 944311cba1 | |||
| bc0c3c1a1b | |||
| 23e0571b2c | |||
| 24c2c2ad5f | |||
| 1b056f0f01 | |||
| 13503d4c19 | |||
| a9a719f1ef | |||
| e4639054e7 | |||
| 264629ed47 | |||
| 5428122609 | |||
| 8d3306ea1f | |||
| b64a779619 | |||
| 975dcfe023 | |||
| 74ba94b66f | |||
| 580fa9ecc3 | |||
| 27e6765a43 | |||
| 9de7c4d8ae | |||
| a98bed0da7 | |||
| e876035fc6 | |||
| f4fd3a8607 | |||
| 83c644d2bf | |||
| 1d4eb97d98 | |||
| a7302326f2 | |||
| 72931c5220 | |||
| b848eabdf3 | |||
| cd2913d6f7 | |||
| ec6da1d383 | |||
| ccc6da2e8c | |||
| 39933d3c74 | |||
| 5ee037a7c3 | |||
| d97b889455 | |||
| 65d160af3b | |||
| 837c4448b0 | |||
| db650b3ea6 | |||
| 76afc98890 | |||
| 8d46a01a10 | |||
| 7a6d664c42 | |||
| 70830478d9 | |||
| c575b7e7a7 | |||
| ec7f1f8a14 | |||
| 24f6c797f2 | |||
| a2885a3283 | |||
| 0143f1e465 | |||
| dc57f9af20 | |||
| 984ede7d55 | |||
| fc398bd1f9 | |||
| 75faf3efaa | |||
| 4f5dd1f399 | |||
| 033ae23b7c | |||
| a0be70d5a6 | |||
| ef29427877 | |||
| 2af0155e6a | |||
| 8130c5cc68 | |||
| 67d23a229f | |||
| 6c9515c385 | |||
| d4f12f6773 | |||
| 65a70996c4 | |||
| 2b8443a1b1 | |||
| c169058915 | |||
| 4c2003d05e | |||
| 42bb758383 | |||
| 26dab9b26b | |||
| d1a4129323 | |||
| a283ddf457 | |||
| b172762288 | |||
| b3da7e84d8 | |||
| 96989be616 | |||
| 9514f15073 | |||
| 1a57eccd38 | |||
| fdfd82207d | |||
| 6e32acfeff | |||
| 999e9cbc36 | |||
| c71f9f3dc2 | |||
| d3e0351d05 | |||
| 4108ecdce9 | |||
| 4bf3e2a1df | |||
| e9da5eeba5 | |||
| d934f4ca89 | |||
| 5b54f9a7be | |||
| cac0276c51 | |||
| c5bd4c650d | |||
| 1b795fa971 | |||
| 4a8bd9a35b | |||
| 4a527dc70b | |||
| 97717beb6d | |||
| 872b693373 | |||
| dcecdc31b5 | |||
| 3212e3f754 | |||
| d6695de462 | |||
| c75efc3e7b | |||
| 58ad0de0db | |||
| 238249309f | |||
| 35dc8aed8f | |||
| 73353dd4ca | |||
| 33a0e265b8 | |||
| 45f197250b | |||
| 13bc2d582e |
@@ -1,4 +1,4 @@
|
||||
# Upstream (michaelshimeles/paper-rs) carries no ArtCraft/Discord branding; keep it that way. Run: perl tools/craft-skills/skills/debrand/debrand.pl --check .
|
||||
# The tree carries no ArtCraft/Discord branding; keep it that way. Run: perl tools/craft-skills/skills/debrand/debrand.pl --check .
|
||||
keep ^(LICENSE-(MIT|APACHE)|\.debrand)$
|
||||
forbid (?i)artcraft
|
||||
forbid (?i)discord
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* @nxtime
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Something does not work as expected
|
||||
labels: bug
|
||||
---
|
||||
|
||||
**What happened**
|
||||
|
||||
**What you expected**
|
||||
|
||||
**Steps to reproduce**
|
||||
1.
|
||||
2.
|
||||
|
||||
**Version and platform**
|
||||
PaperCraft version (the release you installed), OS, and the GPU if it is a rendering problem.
|
||||
|
||||
**Screenshot, recording or project**
|
||||
Attach one if you can. Remove anything private first.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Security vulnerability
|
||||
url: https://github.com/nxtime/papercraft/security/advisories/new
|
||||
about: Report security problems privately, not as an issue.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or a missing capability
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
**The problem**
|
||||
What are you trying to do, and what gets in the way?
|
||||
|
||||
**What you would like**
|
||||
|
||||
**Alternatives you considered**
|
||||
|
||||
**Is it already in ROADMAP.md?**
|
||||
@@ -0,0 +1,24 @@
|
||||
## What and why
|
||||
|
||||
<!-- What changed, and the problem it solves. Link the issue: Fixes #123 -->
|
||||
|
||||
## How I tested it
|
||||
|
||||
<!-- Tests added, commands run, platforms tried. -->
|
||||
|
||||
## AI assistance
|
||||
|
||||
<!-- Which tool helped and how much, or "none". See AI_POLICY.md. You must understand every line. -->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- For UI changes: before and after, at scale 2, in each theme. -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Tests first; new code is covered, error paths included
|
||||
- [ ] `cargo fmt --all`, `cargo clippy --all-targets` and the affected `cargo test --test` targets pass
|
||||
- [ ] Follows [RULES.md](../RULES.md) (no overflow, data safety, one undo step per tool call)
|
||||
- [ ] Docs, tool descriptions and `PROGRESS.md` updated where needed
|
||||
- [ ] Conventional commit messages
|
||||
- [ ] I read and understand every line of this change (AI-assisted or not)
|
||||
@@ -0,0 +1,101 @@
|
||||
# Builds Windows, macOS and Linux on GitHub's runners when a v* tag is pushed, then publishes a
|
||||
# GitHub release with the files once the full test suite (test.yml) passes, then refreshes the
|
||||
# landing page (site.yml). Gitea reads .github/workflows too, so every job is gated on the
|
||||
# server URL: this pipeline only ever runs on github.com.
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Existing tag to build and release (e.g. v0.8.4)
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
TAG: ${{ inputs.tag || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
with: { ref: "${{ inputs.tag || github.ref }}" }
|
||||
|
||||
windows:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { ref: "${{ inputs.tag || github.ref }}" }
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Build and zip
|
||||
shell: pwsh
|
||||
run: |
|
||||
cargo build --release --locked --bins
|
||||
$v = $env:TAG.TrimStart('v')
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$exes = 'papercraft','papercraft-cli','papercraft-mcp','papercraft-relay' | ForEach-Object { "target/release/$_.exe" }
|
||||
Compress-Archive -Path $exes -DestinationPath "dist/papercraft-$v-windows-x86_64.zip"
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: windows, path: dist/* }
|
||||
|
||||
macos:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { ref: "${{ inputs.tag || github.ref }}" }
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with: { targets: aarch64-apple-darwin }
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Package (Apple silicon, ad-hoc signed, not notarized)
|
||||
run: packaging/macos/package.sh --arch aarch64
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: macos, path: dist/release/* }
|
||||
|
||||
linux:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { ref: "${{ inputs.tag || github.ref }}" }
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libssl-dev libxkbcommon-x11-dev libxcb-xkb-dev libx11-xcb-dev libwayland-dev libvulkan-dev libdbus-1-dev
|
||||
- name: Build and tar
|
||||
run: |
|
||||
cargo build --release --locked --bins
|
||||
v="${TAG#v}"
|
||||
mkdir -p dist stage
|
||||
cp target/release/papercraft target/release/papercraft-cli target/release/papercraft-mcp target/release/papercraft-relay stage/
|
||||
tar -C stage -czf "dist/papercraft-$v-linux-x86_64.tar.gz" .
|
||||
tar -C stage -czf "dist/papercraft-cli-$v-linux-x86_64.tar.gz" papercraft-cli papercraft-mcp
|
||||
- uses: actions/upload-artifact@v4
|
||||
with: { name: linux, path: dist/* }
|
||||
|
||||
publish:
|
||||
if: github.server_url == 'https://github.com'
|
||||
needs: [test, windows, macos, linux]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with: { path: files, merge-multiple: true }
|
||||
- name: Create the release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create "$TAG" files/* --repo "$GITHUB_REPOSITORY" --title "PaperCraft ${TAG#v}" \
|
||||
--generate-notes --verify-tag || gh release upload "$TAG" files/* --repo "$GITHUB_REPOSITORY" --clobber
|
||||
|
||||
site:
|
||||
if: github.server_url == 'https://github.com'
|
||||
needs: publish
|
||||
uses: ./.github/workflows/site.yml
|
||||
@@ -0,0 +1,69 @@
|
||||
# Builds the landing page (site/) with the latest GitHub release and commits it to the `pages`
|
||||
# branch, which GitHub Pages serves (Settings → Pages → Deploy from a branch → pages, / (root)).
|
||||
# release.yml calls it after publishing, a daily run catches releases made by hand, and pushes to
|
||||
# the page's sources rebuild it. Nothing is committed when the result is unchanged. Gitea reads
|
||||
# .github/workflows too, so the job is gated on the server URL.
|
||||
name: site
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "23 5 * * *"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- site/**
|
||||
- docs/demo.mp4
|
||||
- docs/screenshots/readme/**
|
||||
- assets/app-icon/papercraft.svg
|
||||
- assets/agents/**
|
||||
- .github/workflows/site.yml
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: site
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# The page comes from the default branch even when a release tag calls this workflow.
|
||||
- uses: actions/checkout@v4
|
||||
with: { ref: "${{ github.event.repository.default_branch }}" }
|
||||
- name: Install ffmpeg
|
||||
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ffmpeg
|
||||
- name: Check out the pages branch
|
||||
run: |
|
||||
echo "PAGES=$RUNNER_TEMP/pages" >> "$GITHUB_ENV"
|
||||
PAGES="$RUNNER_TEMP/pages"
|
||||
if git ls-remote --exit-code --heads origin pages > /dev/null; then
|
||||
git fetch --depth 1 origin pages:refs/remotes/origin/pages
|
||||
git worktree add --detach "$PAGES" origin/pages
|
||||
else
|
||||
git worktree add --orphan -b pages "$PAGES"
|
||||
fi
|
||||
- name: Build
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: sh site/build.sh dist/site "$PAGES"
|
||||
- name: Commit and push
|
||||
run: |
|
||||
cd "${PAGES:?}"
|
||||
# Clearing through git drops files the new build no longer has, and only tracked ones.
|
||||
git rm -rqf --ignore-unmatch .
|
||||
cp -a "$GITHUB_WORKSPACE/dist/site/." .
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "The page is already up to date."
|
||||
exit 0
|
||||
fi
|
||||
tag="$(jq -r .tag release.json)"
|
||||
git -c user.name="github-actions[bot]" \
|
||||
-c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
|
||||
commit -q -m "chore(site): publish the page for $tag"
|
||||
git push origin HEAD:refs/heads/pages
|
||||
@@ -0,0 +1,34 @@
|
||||
# The full test suite. Local runs cover only the targets a change touches (RULES.md, "The machine
|
||||
# you build on"); this is where everything runs, before a release can publish. Run it alone with
|
||||
# `gh workflow run test.yml -f ref=<branch or tag>`. Gitea reads .github/workflows too, so the job
|
||||
# is gated on the server URL.
|
||||
name: test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
type: string
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: Branch or tag to test (default the one you run from)
|
||||
required: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.server_url == 'https://github.com'
|
||||
# Windows: the platform the app is developed on, and its runners render with a software GPU.
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with: { ref: "${{ inputs.ref || github.ref }}" }
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with: { targets: wasm32-unknown-unknown }
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo test --locked --no-fail-fast
|
||||
- run: cargo check --locked --lib --target wasm32-unknown-unknown
|
||||
@@ -0,0 +1,2 @@
|
||||
[ 3482ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 3531ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
@@ -0,0 +1,9 @@
|
||||
[ 210ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 1039ms] [LOG] [Fast Refresh] done in 930ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 1076ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 1086ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 37298ms] [ERROR] Pattern attribute value [0-9() -]{10,16} is not a valid regular expression: Uncaught SyntaxError: Invalid regular expression: /[0-9() -]{10,16}/v: Invalid character in character class @ http://localhost:3000/motorista/entrar:0
|
||||
[ 60872ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 60976ms] [LOG] [Fast Refresh] done in 99ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 61021ms] [LOG] [Fast Refresh] done in 99ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 66490ms] [LOG] [Fast Refresh] done in 99ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
@@ -0,0 +1,13 @@
|
||||
[ 93ms] [LOG] [Fast Refresh] done in 99ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 953ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 963ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 4345ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 4433ms] [LOG] [Fast Refresh] done in 189ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 4548ms] [VERBOSE] [DOM] Password forms should have (optionally hidden) username fields for accessibility: (More info: https://goo.gl/9p2vKq) %o @ http://localhost:3000/entrar/senha:0
|
||||
[ 9731ms] [LOG] [Fast Refresh] done in 5486ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 9777ms] [LOG] [Fast Refresh] done in 5486ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 11070ms] [LOG] [Fast Refresh] done in 5486ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 30758ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=fBVPvwqt_6CU4TU3WWYmu' failed: @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 31764ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=fBVPvwqt_6CU4TU3WWYmu' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 32766ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=fBVPvwqt_6CU4TU3WWYmu' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 33776ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
@@ -0,0 +1,79 @@
|
||||
[ 805ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 813ms] [LOG] [HMR] connected @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 1121ms] [VERBOSE] [DOM] Password forms should have (optionally hidden) username fields for accessibility: (More info: https://goo.gl/9p2vKq) %o @ http://localhost:3000/entrar/senha:0
|
||||
[ 2267ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 2310ms] [LOG] [Fast Refresh] done in 144ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 9621ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 9763ms] [LOG] [Fast Refresh] done in 243ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 48693ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 48749ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 48826ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 51025ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 51539ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 51907ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 107213ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 118297ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 123464ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 125669ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 125771ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 126799ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 126941ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 127929ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 127981ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 154930ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 156620ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 157845ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158106ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158454ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158504ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158748ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158848ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 158955ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 159307ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 159399ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 159488ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 159901ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 159993ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 187894ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 188000ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 205603ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 207208ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 207296ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 207951ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208126ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208244ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208353ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208415ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208590ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208695ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208794ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208878ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 208986ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 209132ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 209234ms] [LOG] [Fast Refresh] done in 39171ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 228278ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 228308ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 238991ms] [LOG] [Fast Refresh] done in 10713ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 239037ms] [LOG] [Fast Refresh] done in 10713ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 239175ms] [LOG] [Fast Refresh] rebuilding @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 239206ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 239602ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 239697ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 264111ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 265916ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 321743ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 333352ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 369410ms] [LOG] [Fast Refresh] done in 3ms @ http://localhost:3000/_next/static/chunks/1bba_next_dist_041c6vio7na_y._.js:719
|
||||
[ 413886ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 414902ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 415904ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 416906ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 417908ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 418911ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 423918ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 428928ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 433950ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 438984ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 444022ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 449184ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
[ 454479ms] [ERROR] WebSocket connection to 'ws://localhost:3000/_next/hmr?id=ruCdDO_vey1tElDs8WYR0' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED @ http://localhost:3000/_next/static/chunks/1bba_next_dist_client_0ru2jkh0js1s8._.js:16225
|
||||
@@ -0,0 +1,28 @@
|
||||
- generic [ref=e2]:
|
||||
- complementary [ref=e3]:
|
||||
- generic [ref=e8]:
|
||||
- generic [ref=e9]: Rota Fresca
|
||||
- generic [ref=e10]: Distribuição de alimentos
|
||||
- generic [ref=e11]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=e12]
|
||||
- paragraph [ref=e13]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=e14]:
|
||||
- link "Precisa de ajuda?" [ref=e16] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=e17]:
|
||||
- generic [ref=e18]:
|
||||
- heading "Entrar" [level=1] [ref=e19]
|
||||
- paragraph [ref=e20]: Use o e-mail da sua conta. Conforme o tipo de conta, pedimos a senha ou mandamos um link de acesso.
|
||||
- generic [ref=e21]:
|
||||
- generic [ref=e22]:
|
||||
- generic [ref=e23]: E-mail
|
||||
- textbox "E-mail" [ref=e24]:
|
||||
- /placeholder: nome@empresa.com.br
|
||||
- button "Continuar" [ref=e25]
|
||||
- generic [ref=e26]: ou
|
||||
- generic [ref=e33]:
|
||||
- paragraph [ref=e34]: É motorista?
|
||||
- paragraph [ref=e35]: Entre pelo app Rota Fresca no celular, com o seu número. Não precisa de e-mail.
|
||||
- generic [ref=e36]:
|
||||
- paragraph [ref=e37]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=e38]: Termos de uso · Privacidade
|
||||
@@ -0,0 +1,20 @@
|
||||
- generic [active] [ref=f1e1]:
|
||||
- generic [ref=f1e2]:
|
||||
- generic [ref=f1e3]:
|
||||
- generic [ref=f1e8]:
|
||||
- generic [ref=f1e9]: Rota Fresca
|
||||
- generic [ref=f1e10]: App do motorista
|
||||
- banner [ref=f1e11]:
|
||||
- heading "Entrar" [level=1] [ref=f1e12]
|
||||
- paragraph [ref=f1e13]: Use o número de celular que a central cadastrou. Mandamos um código, sem senha.
|
||||
- generic [ref=f1e14]:
|
||||
- generic [ref=f1e15]: Celular
|
||||
- generic [ref=f1e16]:
|
||||
- generic [ref=f1e17]: "+55"
|
||||
- textbox "(21) 98877-4410" [ref=f1e18]
|
||||
- generic [ref=f1e19]:
|
||||
- button "Receber código no WhatsApp" [ref=f1e20]
|
||||
- button "Receber por SMS" [ref=f1e21]
|
||||
- paragraph [ref=f1e22]: Trocou de número? Fale com a central.
|
||||
- button "Open Next.js Dev Tools" [ref=f1e28] [cursor=pointer]
|
||||
- alert [ref=f1e32]
|
||||
@@ -0,0 +1,32 @@
|
||||
- generic [active] [ref=f2e1]:
|
||||
- generic [ref=f2e2]:
|
||||
- complementary [ref=f2e3]:
|
||||
- generic [ref=f2e9]:
|
||||
- generic [ref=f2e10]: Rota Fresca
|
||||
- generic [ref=f2e11]: Distribuição de alimentos
|
||||
- generic [ref=f2e12]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f2e13]
|
||||
- paragraph [ref=f2e14]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f2e15]:
|
||||
- link "Precisa de ajuda?" [ref=f2e17] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f2e18]:
|
||||
- generic [ref=f2e19]:
|
||||
- heading "Entrar" [level=1] [ref=f2e20]
|
||||
- paragraph [ref=f2e21]: Use o e-mail da sua conta. Conforme o tipo de conta, pedimos a senha ou mandamos um link de acesso.
|
||||
- generic [ref=f2e22]:
|
||||
- generic [ref=f2e23]:
|
||||
- generic [ref=f2e24]: E-mail
|
||||
- textbox "E-mail" [ref=f2e25]:
|
||||
- /placeholder: nome@empresa.com.br
|
||||
- button "Continuar" [ref=f2e26]
|
||||
- generic [ref=f2e27]: ou
|
||||
- button "Entrar com chave de acesso (passkey)" [ref=f2e31]
|
||||
- generic [ref=f2e35]:
|
||||
- paragraph [ref=f2e36]: É motorista?
|
||||
- paragraph [ref=f2e37]: Entre pelo app Rota Fresca no celular, com o seu número. Não precisa de e-mail.
|
||||
- generic [ref=f2e38]:
|
||||
- paragraph [ref=f2e39]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f2e40]: Termos de uso · Privacidade
|
||||
- button "Open Next.js Dev Tools" [ref=f2e46] [cursor=pointer]
|
||||
- alert [ref=f2e50]
|
||||
@@ -0,0 +1,38 @@
|
||||
- generic [active] [ref=f2e1]:
|
||||
- button "Open Next.js Dev Tools" [ref=f2e46] [cursor=pointer]
|
||||
- alert [ref=f2e50]: Digite sua senha
|
||||
- generic [ref=f2e51]:
|
||||
- complementary [ref=f2e52]:
|
||||
- generic [ref=f2e58]:
|
||||
- generic [ref=f2e59]: Rota Fresca
|
||||
- generic [ref=f2e60]: Distribuição de alimentos
|
||||
- generic [ref=f2e61]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f2e62]
|
||||
- paragraph [ref=f2e63]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f2e64]:
|
||||
- link "Precisa de ajuda?" [ref=f2e66] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f2e67]:
|
||||
- generic [ref=f2e68]:
|
||||
- generic [ref=f2e69]: ana.souza@rotafresca.com.br
|
||||
- button "Trocar" [ref=f2e74]
|
||||
- generic [ref=f2e75]:
|
||||
- heading "Digite sua senha" [level=1] [ref=f2e76]
|
||||
- paragraph [ref=f2e77]: Conta de consultor. Depois da senha pedimos o código do autenticador.
|
||||
- generic [ref=f2e78]:
|
||||
- generic [ref=f2e79]:
|
||||
- generic [ref=f2e80]:
|
||||
- generic [ref=f2e81]: Senha
|
||||
- textbox "Senha" [ref=f2e82]
|
||||
- generic [ref=f2e83]:
|
||||
- generic [ref=f2e84]:
|
||||
- checkbox "Mostrar senha" [ref=f2e85]
|
||||
- checkbox [aria-hidden]
|
||||
- text: Mostrar senha
|
||||
- link "Esqueci minha senha" [ref=f2e86] [cursor=pointer]:
|
||||
- /url: /entrar/redefinir
|
||||
- button "Entrar" [ref=f2e87]
|
||||
- paragraph [ref=f2e88]: Pode colar a senha ou usar o gerenciador de senhas do navegador.
|
||||
- generic [ref=f2e89]:
|
||||
- paragraph [ref=f2e90]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f2e91]: Termos de uso · Privacidade
|
||||
@@ -0,0 +1,40 @@
|
||||
- generic [ref=f2e1]:
|
||||
- button "Open Next.js Dev Tools" [ref=f2e46] [cursor=pointer]
|
||||
- alert [ref=f2e50]: Rota Fresca
|
||||
- generic [ref=f2e51]:
|
||||
- complementary [ref=f2e52]:
|
||||
- generic [ref=f2e58]:
|
||||
- generic [ref=f2e59]: Rota Fresca
|
||||
- generic [ref=f2e60]: Distribuição de alimentos
|
||||
- generic [ref=f2e61]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f2e62]
|
||||
- paragraph [ref=f2e63]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f2e64]:
|
||||
- link "Precisa de ajuda?" [ref=f2e66] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f2e67]:
|
||||
- generic [ref=f2e68]:
|
||||
- generic [ref=f2e69]: ana.souza@rotafresca.com.br
|
||||
- button "Trocar" [ref=f2e74]
|
||||
- generic [ref=f2e75]:
|
||||
- heading "Digite sua senha" [level=1] [ref=f2e76]
|
||||
- paragraph [ref=f2e77]: Conta de consultor. Depois da senha pedimos o código do autenticador.
|
||||
- generic [ref=f2e78]:
|
||||
- generic [ref=f2e79]:
|
||||
- generic [ref=f2e80]:
|
||||
- generic [ref=f2e81]: Senha
|
||||
- textbox "Senha" [active] [invalid] [ref=f2e82]
|
||||
- generic [ref=f2e83]:
|
||||
- generic [ref=f2e84]:
|
||||
- checkbox "Mostrar senha" [ref=f2e85]
|
||||
- checkbox [aria-hidden]
|
||||
- text: Mostrar senha
|
||||
- link "Esqueci minha senha" [ref=f2e86] [cursor=pointer]:
|
||||
- /url: /entrar/redefinir
|
||||
- alert [ref=f2e92]:
|
||||
- generic [ref=f2e93]: Senha incorreta. Confira e tente de novo.
|
||||
- button "Entrar" [ref=f2e87]
|
||||
- paragraph [ref=f2e88]: Pode colar a senha ou usar o gerenciador de senhas do navegador.
|
||||
- generic [ref=f2e89]:
|
||||
- paragraph [ref=f2e90]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f2e91]: Termos de uso · Privacidade
|
||||
@@ -0,0 +1,32 @@
|
||||
- generic [active] [ref=f3e1]:
|
||||
- generic [ref=f3e2]:
|
||||
- complementary [ref=f3e3]:
|
||||
- generic [ref=f3e9]:
|
||||
- generic [ref=f3e10]: Rota Fresca
|
||||
- generic [ref=f3e11]: Distribuição de alimentos
|
||||
- generic [ref=f3e12]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f3e13]
|
||||
- paragraph [ref=f3e14]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f3e15]:
|
||||
- link "Precisa de ajuda?" [ref=f3e17] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f3e18]:
|
||||
- generic [ref=f3e19]:
|
||||
- heading "Entrar" [level=1] [ref=f3e20]
|
||||
- paragraph [ref=f3e21]: Use o e-mail da sua conta. Conforme o tipo de conta, pedimos a senha ou mandamos um link de acesso.
|
||||
- generic [ref=f3e22]:
|
||||
- generic [ref=f3e23]:
|
||||
- generic [ref=f3e24]: E-mail
|
||||
- textbox "E-mail" [ref=f3e25]:
|
||||
- /placeholder: nome@empresa.com.br
|
||||
- button "Continuar" [ref=f3e26]
|
||||
- generic [ref=f3e27]: ou
|
||||
- button "Entrar com chave de acesso (passkey)" [ref=f3e31]
|
||||
- generic [ref=f3e35]:
|
||||
- paragraph [ref=f3e36]: É motorista?
|
||||
- paragraph [ref=f3e37]: Entre pelo app Rota Fresca no celular, com o seu número. Não precisa de e-mail.
|
||||
- generic [ref=f3e38]:
|
||||
- paragraph [ref=f3e39]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f3e40]: Termos de uso · Privacidade
|
||||
- button "Open Next.js Dev Tools" [ref=f3e46] [cursor=pointer]
|
||||
- alert [ref=f3e50]
|
||||
@@ -0,0 +1,38 @@
|
||||
- generic [active] [ref=f3e1]:
|
||||
- button "Open Next.js Dev Tools" [ref=f3e46] [cursor=pointer]
|
||||
- alert [ref=f3e50]
|
||||
- generic [ref=f3e51]:
|
||||
- complementary [ref=f3e52]:
|
||||
- generic [ref=f3e58]:
|
||||
- generic [ref=f3e59]: Rota Fresca
|
||||
- generic [ref=f3e60]: Distribuição de alimentos
|
||||
- generic [ref=f3e61]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f3e62]
|
||||
- paragraph [ref=f3e63]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f3e64]:
|
||||
- link "Precisa de ajuda?" [ref=f3e66] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f3e67]:
|
||||
- generic [ref=f3e68]:
|
||||
- generic [ref=f3e69]: ana.souza@rotafresca.com.br
|
||||
- button "Trocar" [ref=f3e74]
|
||||
- generic [ref=f3e75]:
|
||||
- heading "Digite sua senha" [level=1] [ref=f3e76]
|
||||
- paragraph [ref=f3e77]: Conta de consultor. Depois da senha pedimos o código do autenticador.
|
||||
- generic [ref=f3e78]:
|
||||
- generic [ref=f3e79]:
|
||||
- generic [ref=f3e80]:
|
||||
- generic [ref=f3e81]: Senha
|
||||
- textbox "Senha" [ref=f3e82]
|
||||
- generic [ref=f3e83]:
|
||||
- generic [ref=f3e84]:
|
||||
- checkbox "Mostrar senha" [ref=f3e85]
|
||||
- checkbox [aria-hidden]
|
||||
- text: Mostrar senha
|
||||
- link "Esqueci minha senha" [ref=f3e86] [cursor=pointer]:
|
||||
- /url: /entrar/redefinir
|
||||
- button "Entrar" [ref=f3e87]
|
||||
- paragraph [ref=f3e88]: Pode colar a senha ou usar o gerenciador de senhas do navegador.
|
||||
- generic [ref=f3e89]:
|
||||
- paragraph [ref=f3e90]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f3e91]: Termos de uso · Privacidade
|
||||
@@ -0,0 +1,33 @@
|
||||
- generic [active] [ref=f3e1]:
|
||||
- button "Open Next.js Dev Tools" [ref=f3e46] [cursor=pointer]
|
||||
- alert [ref=f3e50]
|
||||
- generic [ref=f3e92]:
|
||||
- complementary [ref=f3e93]:
|
||||
- generic [ref=f3e99]:
|
||||
- generic [ref=f3e100]: Rota Fresca
|
||||
- generic [ref=f3e101]: Distribuição de alimentos
|
||||
- generic [ref=f3e102]:
|
||||
- heading "Do depósito à prateleira, na hora certa." [level=2] [ref=f3e103]
|
||||
- paragraph [ref=f3e104]: Pedidos, entregas e notas da sua rede no mesmo lugar.
|
||||
- main [ref=f3e105]:
|
||||
- link "Precisa de ajuda?" [ref=f3e107] [cursor=pointer]:
|
||||
- /url: mailto:suporte@rotafresca.com.br
|
||||
- generic [ref=f3e108]:
|
||||
- heading "Confirme que é você" [level=1] [ref=f3e113]
|
||||
- generic [ref=f3e114]:
|
||||
- generic [ref=f3e115]:
|
||||
- generic [ref=f3e116]:
|
||||
- generic [ref=f3e117]: Código
|
||||
- textbox "Código" [ref=f3e118]
|
||||
- generic [ref=f3e119]:
|
||||
- checkbox "Não pedir de novo neste computador por 30 dias" [checked] [ref=f3e120]
|
||||
- checkbox [checked] [aria-hidden]
|
||||
- text: Não pedir de novo neste computador por 30 dias
|
||||
- button "Confirmar" [ref=f3e121]
|
||||
- generic [ref=f3e122]:
|
||||
- link "Usar um código de recuperação" [ref=f3e123] [cursor=pointer]:
|
||||
- /url: /entrar/codigo?recuperacao=1
|
||||
- paragraph [ref=f3e124]: Perdeu o celular? Peça ao administrador da conta para refazer o autenticador.
|
||||
- generic [ref=f3e125]:
|
||||
- paragraph [ref=f3e126]: Recebeu um convite? Abra o link do e-mail para criar seu acesso.
|
||||
- paragraph [ref=f3e127]: Termos de uso · Privacidade
|
||||
@@ -0,0 +1,47 @@
|
||||
- generic [active] [ref=f3e1]:
|
||||
- button "Open Next.js Dev Tools" [ref=f3e46] [cursor=pointer]
|
||||
- alert [ref=f3e50]: Bom dia, Ana
|
||||
- generic [ref=f3e128]:
|
||||
- complementary [ref=f3e129]:
|
||||
- generic [ref=f3e130]:
|
||||
- generic [ref=f3e136]:
|
||||
- generic [ref=f3e137]: Rota Fresca
|
||||
- generic [ref=f3e138]: Distribuição de alimentos
|
||||
- navigation [ref=f3e139]:
|
||||
- link "Painel" [ref=f3e140] [cursor=pointer]:
|
||||
- /url: /painel
|
||||
- link "Roteiro do dia" [ref=f3e143] [cursor=pointer]:
|
||||
- /url: /roteiro
|
||||
- link "Lojas (PDVs)" [ref=f3e146] [cursor=pointer]:
|
||||
- /url: /lojas
|
||||
- link "Pedidos" [ref=f3e149] [cursor=pointer]:
|
||||
- /url: /pedidos
|
||||
- link "Entregas" [ref=f3e152] [cursor=pointer]:
|
||||
- /url: /entregas
|
||||
- link "Validade e lotes" [ref=f3e155] [cursor=pointer]:
|
||||
- /url: /validade
|
||||
- link "Relatórios" [ref=f3e158] [cursor=pointer]:
|
||||
- /url: /relatorios
|
||||
- link "Integrações e notas" [ref=f3e161] [cursor=pointer]:
|
||||
- /url: /integracoes
|
||||
- link "Leitura de documentos" [ref=f3e164] [cursor=pointer]:
|
||||
- /url: /documentos
|
||||
- link "Contatos e mensagens" [ref=f3e167] [cursor=pointer]:
|
||||
- /url: /contatos
|
||||
- link "Configurações" [ref=f3e170] [cursor=pointer]:
|
||||
- /url: /configuracoes
|
||||
- generic [ref=f3e173]:
|
||||
- generic [aria-hidden] [ref=f3e174]: A
|
||||
- generic [ref=f3e175]:
|
||||
- generic [ref=f3e176]: Ana Souza
|
||||
- generic [ref=f3e177]: Consultora · Zona Oeste
|
||||
- main [ref=f3e178]:
|
||||
- generic [ref=f3e179]:
|
||||
- generic [ref=f3e180]:
|
||||
- heading "Bom dia, Ana" [level=1] [ref=f3e181]
|
||||
- paragraph [ref=f3e182]: domingo, 25 de outubro
|
||||
- generic [ref=f3e183]:
|
||||
- textbox "Buscar loja, pedido ou produto" [ref=f3e187]
|
||||
- button "Notificações" [ref=f3e188]
|
||||
- button "Importar / Exportar" [ref=f3e189]
|
||||
- button "Novo pedido" [ref=f3e190]
|
||||
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 344 KiB |
@@ -0,0 +1,77 @@
|
||||
# Guidance for coding agents
|
||||
|
||||
PaperCraft is a native Rust design and whiteboard canvas (egui, wgpu, winit) that AI agents drive
|
||||
over MCP and a CLI. Read [CONTRIBUTING.md](CONTRIBUTING.md) and [RULES.md](RULES.md) first; this
|
||||
file is the short version for agents, and [AI_POLICY.md](AI_POLICY.md) says what the person using
|
||||
you is accountable for.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
cargo fmt --all
|
||||
cargo clippy --all-targets
|
||||
cargo test --test incremental # only the targets your change touches
|
||||
cargo test --lib editor::changes # unit tests of one module
|
||||
cargo check --lib --target wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
Do not run the full suite locally: it builds dozens of test binaries. It runs on GitHub before
|
||||
each release (`.github/workflows/test.yml`). `tests/update.rs` cannot run on Windows outside CI
|
||||
(it needs elevation); that failure is known.
|
||||
|
||||
Builds run on the person's own machine, so mind its resources (RULES.md, "The machine you build
|
||||
on"): check free memory and running builds first, cap jobs at no more than half the cores
|
||||
(`cargo test -j 8 -- --test-threads=8` on 16 cores), run at low priority, run one heavy job at a
|
||||
time, and stop at once if they say it is slow.
|
||||
|
||||
## Running the app safely
|
||||
|
||||
Never run a test copy against the person's real projects or MCP port, and never close or restart
|
||||
their running PaperCraft. Give yours its own folder and port, and keep its PID so you can stop only
|
||||
your own process:
|
||||
|
||||
```sh
|
||||
PAPERCRAFT_HOME=/tmp/pc PAPERCRAFT_MCP_PORT=29998 ./target/release/papercraft
|
||||
```
|
||||
|
||||
Do not stop processes by name.
|
||||
|
||||
## Layout
|
||||
|
||||
`src/model` (document), `src/editor` (tools and edits), `src/render` (wgpu), `src/mcp` (tools,
|
||||
HTML/CSS mapping, lint), `src/ui` (egui panels), `src/io` (import and export), `src/project`
|
||||
(store and formats), `tests/` (scenario tests), `specs/` (feature specs), `skills/` (agent
|
||||
skills). The README's project layout lists the rest.
|
||||
|
||||
## Conventions
|
||||
|
||||
* Tests first; match the style of the tests next to yours, and check the behaviour is not already
|
||||
covered.
|
||||
* Changes to the document go through `Editor::edit`: one transaction, one undo step.
|
||||
* Handle errors; do not `unwrap` user data and do not discard errors with `let _ =`.
|
||||
* Comments explain *why*, never summarise the code. No dead code, debug prints or
|
||||
commented-out blocks.
|
||||
* Files stay under about 800 lines; split by feature.
|
||||
* Reuse the shared UI kit and theme instead of one-off values. Nothing may overflow its container.
|
||||
* Writes to projects are atomic and format changes ship a migration (see RULES.md, "Data safety").
|
||||
* Adding or removing an MCP tool means a test in `tests/mcp.rs`, a precise description, and the
|
||||
tool count updated in the README and ROADMAP.
|
||||
* Commits are conventional (`feat(scope): summary`) and focused. Do not commit, push or tag unless
|
||||
the person asked you to.
|
||||
|
||||
## Designing with PaperCraft (skills, examples, screenshots)
|
||||
|
||||
* Themes are modes, never copies: one artboard per screen, a value per mode on each variable, and
|
||||
`set_variable_mode` to switch. Never duplicate artboards for light and dark.
|
||||
* Every colour on a themed screen is a variable.
|
||||
* Check each artboard for overflow, programmatically and by screenshot in each mode, and run
|
||||
`lint_design`.
|
||||
* Release design locks when you finish.
|
||||
|
||||
## Avoid
|
||||
|
||||
* Creative additions nobody asked for; stay inside the task.
|
||||
* Trivial tests, wide or optional types added to appease the compiler, and swallowed errors.
|
||||
* Editing generated files, lockfiles or release artifacts by hand, or touching files unrelated to
|
||||
the task. Leave other people's uncommitted work alone.
|
||||
@@ -0,0 +1,40 @@
|
||||
# AI usage policy
|
||||
|
||||
PaperCraft is built to be driven by AI agents, and many of its contributors use them too. AI tools
|
||||
are welcome here. What we ask is that a person stays accountable for everything submitted.
|
||||
|
||||
## For contributors
|
||||
|
||||
* **You own what you submit.** Read every line of your pull request before asking for review. If
|
||||
you cannot explain what a change does and how it fits the rest of the system without asking an
|
||||
AI, do not submit it yet.
|
||||
* **Disclose meaningful AI help.** Say which tool you used and roughly how much it wrote in the
|
||||
pull request description (for example "drafted with Claude Code, reviewed and tested by me").
|
||||
Autocomplete and spell-checking do not need a note.
|
||||
* **Write issues yourself.** An agent can help you research, but the report should be in your own
|
||||
words, trimmed to what matters, with reproduction steps you ran. Long generated text that nobody
|
||||
checked puts the burden on maintainers.
|
||||
* **Think before you generate.** Agents jump to code and make their own job easy: trivial tests,
|
||||
wide or optional types, swallowed errors, redundant comments, copied local patterns that do not
|
||||
fit. Push back on all of it. [RULES.md](RULES.md) lists the standards to hold the output to.
|
||||
* **Verify.** Run the checks in [CONTRIBUTING.md](CONTRIBUTING.md), use the app, and attach
|
||||
screenshots for UI changes. "The agent said it works" is not a test result.
|
||||
* **Answer automated review.** If a bot reviews your pull request, fix its findings or reply with
|
||||
a one-line reason a finding does not apply.
|
||||
* **No bulk or drive-by pull requests.** Many small generated changes across the tree, or changes
|
||||
to something nobody asked for, will be closed. Open an issue first for anything beyond a small
|
||||
fix.
|
||||
|
||||
Pull requests that clearly skip these steps may be closed without a detailed review. Repeated
|
||||
low-effort submissions can lead to a block, under the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## For coding agents
|
||||
|
||||
Read [AGENTS.md](AGENTS.md). It holds the commands, conventions and guardrails an agent working in
|
||||
this repository should follow.
|
||||
|
||||
## Why
|
||||
|
||||
Every issue and pull request is read by a person. Review time is the scarce resource, and a change
|
||||
nobody understood costs more than it saves. AI that is reviewed, tested and understood by the
|
||||
person submitting it is a good way to build PaperCraft; AI used to skip that work is not.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Code of Conduct
|
||||
|
||||
PaperCraft is built by people with different backgrounds and experience. We want everyone to be
|
||||
able to contribute without harassment.
|
||||
|
||||
## Our standard
|
||||
|
||||
Be respectful and constructive. Assume good intent, give specific and kind feedback, and accept
|
||||
it the same way. Disagree about the work, not the person.
|
||||
|
||||
Not acceptable:
|
||||
|
||||
* harassment, insults, slurs, or personal or political attacks;
|
||||
* sexualised language or imagery, or unwelcome attention;
|
||||
* threats, doxxing, or publishing someone's private information;
|
||||
* sustained disruption of discussions, or pressure to merge or answer;
|
||||
* anything else a reasonable person would consider inappropriate in a professional setting.
|
||||
|
||||
## Scope
|
||||
|
||||
This applies in issues, pull requests, discussions, commit messages and any space where someone
|
||||
represents the project.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Maintainers may edit or remove comments, close or lock threads, and block people who break these
|
||||
rules. To report a problem, use GitHub's "Report content" on the comment, or contact a maintainer
|
||||
through a private channel on their GitHub profile. Reports are handled privately and in good
|
||||
faith, and retaliation against a reporter is itself a violation.
|
||||
|
||||
The spirit of this text follows the [Contributor Covenant](https://www.contributor-covenant.org).
|
||||
@@ -0,0 +1,129 @@
|
||||
# Contributing to PaperCraft
|
||||
|
||||
Thanks for wanting to help. PaperCraft is an alpha, so there is plenty to do and the bar for a
|
||||
first contribution is low: a typo fix, a clearer error message, a missing test or a one-line bug
|
||||
fix are all welcome.
|
||||
|
||||
Please read [RULES.md](RULES.md) before writing code: it holds the standards a change is judged by.
|
||||
By taking part you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). Using an AI tool is fine;
|
||||
the [AI usage policy](AI_POLICY.md) says what you stay responsible for, and coding agents should read
|
||||
[AGENTS.md](AGENTS.md).
|
||||
|
||||
## Ways to help
|
||||
|
||||
* **Report a bug** or **suggest a feature** with an [issue](https://github.com/nxtime/papercraft/issues/new/choose).
|
||||
A screenshot or a short recording helps; so does the project folder (or a page of it) that
|
||||
triggers the bug.
|
||||
* **Pick something from [ROADMAP.md](ROADMAP.md).** Rows marked `todo` or `partial` are open. Say
|
||||
which one you are taking in an issue first, so two people do not build the same thing.
|
||||
* **Improve the docs, skills or screenshots.** [docs/](docs), [skills/](skills) and the README are
|
||||
as important as code.
|
||||
* **Review a pull request.** A second pair of eyes is always useful.
|
||||
|
||||
Security problems do not go in public issues: see [SECURITY.md](SECURITY.md).
|
||||
|
||||
## Before you start
|
||||
|
||||
For anything bigger than a small fix, open an issue (or comment on one) describing the problem and
|
||||
how you plan to solve it. Large features start as a short spec in [specs/](specs): a `spec.md`
|
||||
(why, what, what is out of scope) and a `plan.md`. [specs/component-props](specs/component-props)
|
||||
shows the shape. A spec agreed up front saves a rewrite later.
|
||||
|
||||
## Set up
|
||||
|
||||
You need Rust 1.95 or newer (`rust-version` in `Cargo.toml`) and the platform libraries listed in
|
||||
the README's [Build from source](README.md#build-from-source).
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nxtime/papercraft
|
||||
cd papercraft
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Run a test copy next to your own PaperCraft so it cannot touch your real projects or collide with
|
||||
the running app's MCP port:
|
||||
|
||||
```sh
|
||||
PAPERCRAFT_HOME=/tmp/pc PAPERCRAFT_MCP_PORT=29998 ./target/release/papercraft
|
||||
```
|
||||
|
||||
`PAPERCRAFT_HOME` moves the project root and the config folder; `PAPERCRAFT_MCP_PORT` moves the
|
||||
MCP server. Always set both when developing.
|
||||
|
||||
The code depends on [`craft-ui`](https://git.marcospaulo.dev.br/masi/craft-libs), which Cargo
|
||||
fetches from a public git repository at the revision pinned in `Cargo.toml`.
|
||||
|
||||
## Make the change
|
||||
|
||||
1. Fork the repository and branch from `main` (`fix/short-name`, `feat/short-name`).
|
||||
2. **Write the test first.** Fix a bug by adding a test that fails, then make it pass. See
|
||||
[Tests](#tests).
|
||||
3. Keep the change focused. One concern per pull request; unrelated cleanups go in their own.
|
||||
4. Run the checks below and fix what they report.
|
||||
5. Update the docs the change touches: the README, `docs/`, the tool description for an MCP tool,
|
||||
the skills if agents should now behave differently.
|
||||
6. Add a short entry to [PROGRESS.md](PROGRESS.md) for user-visible changes.
|
||||
|
||||
### Checks
|
||||
|
||||
```sh
|
||||
cargo fmt --all
|
||||
cargo clippy --all-targets
|
||||
cargo test --test <target> # the targets your change touches
|
||||
cargo check --lib --target wasm32-unknown-unknown # the web build
|
||||
```
|
||||
|
||||
Each file in `tests/` is its own binary linking the whole app, so the full suite is slow and heavy
|
||||
on a desktop. It runs on GitHub instead: before every release (`.github/workflows/test.yml`), or
|
||||
on demand with `gh workflow run test.yml -f ref=<branch>`.
|
||||
|
||||
Keep the tree free of the third-party branding patterns listed in `.debrand`.
|
||||
|
||||
### Tests
|
||||
|
||||
* Unit tests sit next to the code in `#[cfg(test)]` modules.
|
||||
* Scenario tests live in [tests/](tests). They drive the editor with synthetic pointer and
|
||||
keyboard events; `tests/mcp.rs` covers every MCP tool on a headless session.
|
||||
* Aim for **80% coverage** on code you add or change, including the error paths.
|
||||
* GPU-dependent tests skip when no adapter exists. Set `PAPERCRAFT_REQUIRE_GPU=1` to make them
|
||||
fail instead.
|
||||
* `tests/update.rs` cannot run on Windows (it needs elevation); that failure is known and not
|
||||
yours.
|
||||
* For UI work, run the app, look at it, and attach a screenshot. Headless rendering needs no
|
||||
display: `papercraft --screenshot ui.png --demo --size 1440x900 --scale 2`.
|
||||
|
||||
## Commits
|
||||
|
||||
[Conventional commits](https://www.conventionalcommits.org): `type(scope): summary`, with the
|
||||
types `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf` and `ci`.
|
||||
|
||||
```
|
||||
fix(ui): keep the review row's revert button off the hover edge
|
||||
feat(mcp): add set_screens
|
||||
docs(readme): add the demo video
|
||||
```
|
||||
|
||||
Write the summary in the imperative, under about 72 characters, and use the body to say *why*.
|
||||
Reference the issue (`Fixes #123`).
|
||||
|
||||
## Pull requests
|
||||
|
||||
* Fill in the template: what changed, why, how you tested it, screenshots for UI changes.
|
||||
* Keep it reviewable. If it grows past a few hundred lines of non-generated code, split it.
|
||||
* The branch must be up to date with `main`; rebase rather than merge.
|
||||
* Expect review comments; they are about the code, not you. Push fixes as new commits and we
|
||||
squash on merge.
|
||||
* A maintainer merges once a review is approved.
|
||||
|
||||
## Releases
|
||||
|
||||
Maintainers cut releases. A `v*` tag pushed to GitHub builds Windows, macOS and Linux and
|
||||
publishes them (`.github/workflows/release.yml`). Contributors do not need to bump versions.
|
||||
|
||||
## Licensing
|
||||
|
||||
PaperCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE). Unless you
|
||||
state otherwise, anything you submit is licensed under the same terms, with no extra conditions.
|
||||
Only submit work you wrote or have the right to submit. Code ported from another project needs a
|
||||
compatible license and an entry in [THIRD_PARTY.md](THIRD_PARTY.md); bundled assets go in
|
||||
[ATTRIBUTION.md](ATTRIBUTION.md).
|
||||
@@ -33,6 +33,17 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
@@ -192,6 +203,112 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.1.5",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix 1.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 1.1.5",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.92"
|
||||
@@ -316,6 +433,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
@@ -334,6 +460,19 @@ dependencies = [
|
||||
"objc2 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
@@ -360,6 +499,12 @@ dependencies = [
|
||||
"syn 3.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
@@ -422,6 +567,15 @@ dependencies = [
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.7.0"
|
||||
@@ -469,6 +623,16 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "5.4.1"
|
||||
@@ -620,7 +784,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-ui"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.marcospaulo.dev.br/masi/craft-libs?rev=22544b6d619d4f16779b8bdfa3f0b642a86fc065#22544b6d619d4f16779b8bdfa3f0b642a86fc065"
|
||||
source = "git+https://git.marcospaulo.dev.br/masi/craft-libs?rev=9c470aa935c4ce48f2ba4f0a17f9410c0c8660fb#9c470aa935c4ce48f2ba4f0a17f9410c0c8660fb"
|
||||
dependencies = [
|
||||
"egui",
|
||||
]
|
||||
@@ -683,6 +847,35 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus-secret-service"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"block-padding",
|
||||
"cbc",
|
||||
"dbus",
|
||||
"fastrand",
|
||||
"hkdf",
|
||||
"num",
|
||||
"once_cell",
|
||||
"sha2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.1"
|
||||
@@ -722,6 +915,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -894,6 +1088,33 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "2.0.0"
|
||||
@@ -979,6 +1200,26 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
|
||||
dependencies = [
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.5.0"
|
||||
@@ -1171,6 +1412,19 @@ version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.34"
|
||||
@@ -1231,6 +1485,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
@@ -1386,6 +1651,30 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.5.0"
|
||||
@@ -1708,6 +1997,16 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.2"
|
||||
@@ -1861,6 +2160,22 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dbus-secret-service",
|
||||
"log",
|
||||
"secret-service",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"windows-sys 0.60.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos-egl"
|
||||
version = "6.0.0"
|
||||
@@ -1896,6 +2211,15 @@ version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
@@ -2042,6 +2366,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -2157,7 +2490,7 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
@@ -2192,6 +2525,19 @@ dependencies = [
|
||||
"jni-sys 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
@@ -2210,6 +2556,69 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2698,6 +3107,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
@@ -2709,7 +3128,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "papercraft"
|
||||
version = "0.5.0"
|
||||
version = "0.9.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"axum",
|
||||
@@ -2727,6 +3146,8 @@ dependencies = [
|
||||
"i_overlay",
|
||||
"image",
|
||||
"js-sys",
|
||||
"keyring",
|
||||
"libc",
|
||||
"log",
|
||||
"lyon",
|
||||
"miniz_oxide 0.9.1",
|
||||
@@ -2740,6 +3161,7 @@ dependencies = [
|
||||
"resvg",
|
||||
"rfd",
|
||||
"rmcp",
|
||||
"ruzstd",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2753,10 +3175,18 @@ dependencies = [
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"wgpu",
|
||||
"windows-sys 0.61.2",
|
||||
"winit",
|
||||
"winresource",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@@ -2837,6 +3267,17 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.34"
|
||||
@@ -2956,7 +3397,7 @@ checksum = "1f21b97672d2dc848e7b25701ab4535618b92f4861c13cc3f7f7bed52ad3c8da"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"indexmap",
|
||||
"nix",
|
||||
"nix 0.31.3",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows",
|
||||
@@ -3010,13 +3451,24 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
@@ -3031,6 +3483,16 @@ dependencies = [
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
@@ -3041,6 +3503,15 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.5"
|
||||
@@ -3385,6 +3856,15 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "ruzstd"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8"
|
||||
dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
@@ -3460,6 +3940,38 @@ dependencies = [
|
||||
"tiny-skia 0.11.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secret-service"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"cbc",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"hkdf",
|
||||
"num",
|
||||
"once_cell",
|
||||
"rand 0.8.8",
|
||||
"serde",
|
||||
"sha2",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
@@ -3560,6 +4072,17 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.2"
|
||||
@@ -3592,6 +4115,17 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
@@ -3801,6 +4335,12 @@ dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.16.1"
|
||||
@@ -3860,10 +4400,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.5",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4226,6 +4766,12 @@ dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
version = "0.5.1"
|
||||
@@ -4241,6 +4787,17 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -5253,6 +5810,16 @@ version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkbcommon-dl"
|
||||
version = "0.4.2"
|
||||
@@ -5307,6 +5874,62 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix 0.29.0",
|
||||
"ordered-stream",
|
||||
"rand 0.8.8",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.62"
|
||||
@@ -5353,6 +5976,20 @@ name = "zeroize"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
@@ -5399,6 +6036,34 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.1.2+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.5.3"
|
||||
@@ -5413,3 +6078,40 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "papercraft"
|
||||
version = "0.5.0"
|
||||
version = "0.9.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
description = "A native infinite-canvas app for planning, working and sketching"
|
||||
@@ -44,7 +44,7 @@ env_logger = "0.11"
|
||||
rfd = "0.17"
|
||||
fastrand = "2"
|
||||
dirs = "6"
|
||||
craft-ui = { version = "0.1.0", git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "22544b6d619d4f16779b8bdfa3f0b642a86fc065" }
|
||||
craft-ui = { version = "0.1.0", git = "https://git.marcospaulo.dev.br/masi/craft-libs", rev = "9c470aa935c4ce48f2ba4f0a17f9410c0c8660fb" }
|
||||
i_overlay = "9.0.1"
|
||||
miniz_oxide = "0.9.1"
|
||||
usvg = "0.48"
|
||||
@@ -93,8 +93,18 @@ axum = { version = "0.8", default-features = false, features = ["http1", "tokio"
|
||||
tokio-util = "0.7"
|
||||
# Shared-project sync; the system TLS stack so https servers work without bundling one.
|
||||
reqwest = { version = "0.13", default-features = false, features = ["native-tls"] }
|
||||
# Anthropic API key in the OS keychain (chat API backend). Linux uses the Secret Service (persistent; keyutils is not).
|
||||
# libc: SIGINT to the spawned chat CLIs.
|
||||
keyring = { version = "3", default-features = false, features = ["apple-native", "windows-native", "sync-secret-service", "crypto-rust"] }
|
||||
libc = "0.2"
|
||||
# Pages and versions on disk are zstd-compressed JSON (`project::packed`).
|
||||
zstd = "0.13"
|
||||
|
||||
# Browser build (`src/web.rs`): canvas + WebGPU/WebGL surface, DOM events instead of winit.
|
||||
# Process RAM and CPU readings for the performance overlay (`ui::perf`).
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61", features = ["Win32_Foundation", "Win32_System_ProcessStatus", "Win32_System_Threading"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wgpu = { version = "30", features = ["webgl"] }
|
||||
wasm-bindgen = "0.2"
|
||||
@@ -102,4 +112,6 @@ wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
# zstd without C, for the browser: reads the packed files native builds write.
|
||||
ruzstd = "0.8"
|
||||
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "KeyboardEvent", "PointerEvent", "WheelEvent", "ClipboardEvent", "DataTransfer", "MediaQueryList", "Location", "Performance", "Navigator", "Clipboard", "CssStyleDeclaration", "console", "WebSocket", "MessageEvent", "CloseEvent", "ErrorEvent", "Storage", "Crypto", "DragEvent", "FileList", "File", "Blob", "Request", "RequestInit", "Response"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Progress
|
||||
|
||||
Last updated: 2026-10-21.
|
||||
Last updated: 2026-10-24.
|
||||
|
||||
## Phase 3: local-first design and whiteboard tool
|
||||
|
||||
@@ -76,12 +76,192 @@ are credited in `THIRD_PARTY.md`.
|
||||
* **Not carried over**: comments, design tokens, AI image generation, video export, teams;
|
||||
moving layers between pages; remote image URLs (see README Known gaps).
|
||||
|
||||
### 2026-10-28: overflow lint rule and `get_bounds`
|
||||
|
||||
* `lint_design` has an `overflow` rule (`src/mcp/lint_overflow.rs`): layers past their artboard or clipping parent, and text taller than its box. Scrolling frames and what is pinned in them are exempt; it is never auto-fixed. It is a pure function over a node set, so a scope filter can pass any subset.
|
||||
* New tool `get_bounds {nodeIds?, pageId?, depth?}` (`src/mcp/bounds.rs`): every layer's rect in one read (about 2000 rows per call, then `continueWith`). Tool count is now 87 (one more than before; if another branch also adds a tool, recount). Tests: `tests/mcp_bounds.rs`.
|
||||
|
||||
### 2026-10-28: agent reads no longer look like edits
|
||||
|
||||
* Read-only tool calls (`get_node_info`, `get_children`, ...) have honest verbs ("Looking at the design") and light the "Working" pill for 1.5 s instead of 6 s, so it goes out right after a sweep ends.
|
||||
* A sweep of reads draws at most one layer outline (a read outlines only when no other read ran in the last 2 s) and no longer pans the follow-agent camera; edits behave as before. "Is this a read" comes from `mcp::tools::edits_doc`.
|
||||
|
||||
### 2026-10-28: 0.9.0 (presentations, critique, several agents, lighter chats)
|
||||
|
||||
* Export the screens as a presentation PDF: cover, contents and a slide per screen (File menu, palette, `--format presentation`, `export_presentation`).
|
||||
* `critique_design` reviews finished work through personas (vision, UX, flow, QA, visual, accessibility, copy, end-user); `design_lessons` keeps the findings the person approved for every later design.
|
||||
* Several agents on one project: named CLI agents (`--as`), conflict reports and `ifRev` on writes, `claim_nodes` / `release_claims` / `list_claims`, scoped delegate builds and per-agent undo. 94 tools.
|
||||
* Screens keep their device height and scroll what does not fit; sections group screens on the canvas; pages can carry icons.
|
||||
* UI kits for shadcn, MUI, Mantine, Bootstrap and Chakra; `write_html` follows the browser more closely.
|
||||
* Memory: chat screenshots move to files (kept 14 days), thumbnails stay small, canvas images decode only for the GPU. A long chat dropped from about 1 GB to about 400 MB.
|
||||
* Fix: long answer options in a question card wrap instead of widening the whole chat.
|
||||
|
||||
### 2026-10-28: 0.8.4 (demo video, fresh screenshots)
|
||||
|
||||
* README opens with a time-lapse of designing a habit-tracker app (`docs/demo.gif`, full video `docs/demo.mp4`), and every screenshot is retaken from that design.
|
||||
* Fix: the Performance overlay reads its settings from the project config folder, so `PAPERCRAFT_HOME` isolates it too.
|
||||
|
||||
### 2026-10-28: 0.8.3 (menus, panel grips, narrow windows)
|
||||
|
||||
* Menus open 6 px below their button, are at least 180 px wide and show an icon beside each item. Below 1100 px the hamburger opens every menu in one popup.
|
||||
* Side panels: a slim rounded grip in the gap resizes them (double click resets) and `/` hides or shows both. Panels size to their content and step aside when the window is too narrow for the canvas.
|
||||
* Sharper window buttons (pixel-snapped glyphs); the hamburger morphs into a back arrow while the File / Edit strip slides open and shut.
|
||||
* Fixes: lint Fix buttons no longer cover the issue text; review rows keep their badge and revert button off the hover edge; the Export button stays on one line.
|
||||
|
||||
### 2026-10-28: 0.8.2 (refresh-rate cap, accurate RAM/CPU, toolbar)
|
||||
|
||||
* Settings > Performance > Refresh rate is now a real frame cap applied live (30 to 360 Hz, 60 by default); **Auto** draws as fast as the display allows and is the default on macOS.
|
||||
* Performance Monitor: RAM is the private working set and CPU is divided by logical cores on Windows, so both match Task Manager.
|
||||
* Windows and Linux toolbar: a hamburger button shows or hides the File / Edit strip (remembered); the close button uses the theme's danger colour on the acrylic surface, with hover animation and a pointing-hand cursor.
|
||||
|
||||
### 2026-10-28: 0.8.1 (Performance Monitor on Windows, Design tab speed)
|
||||
|
||||
* Performance Monitor: RAM (working set) and CPU now show on Windows; before they were `n/a` there. The overlay and slow-frame badge judge frames against a refresh rate you pick in Settings > Performance: 30 to 360 Hz (144, 165 and 240 included), 60 by default.
|
||||
* Design tab: the lint and the open-feedback scan are kept until the document changes instead of walking every node each frame; a 12.6k-node page no longer drops to a few FPS while the tab is open.
|
||||
* Windows and Linux: window buttons in the toolbar; controls and acrylic come from craft-ui.
|
||||
|
||||
### 2026-10-27: 0.8.0 (screens, packed pages, canvas performance)
|
||||
|
||||
* Screens (`specs/screens`): a frame can be declared a screen or an overlay (drawer, modal, sheet) with `set_screens`, inspector toggles, or Declare N screens on the page. Agent edits to layers hidden behind an overlay or opaque layer in their screen are refused unless `allowHidden`; partly covered ones are reported with their visible share. `get_basic_info` lists screens, `get_node_info` tells a layer's visibility, `lint_design` skips hidden layers, On click lists screens, nested screens get canvas labels.
|
||||
* Pages and saved versions are stored as zstd-compressed compact JSON (`.json.zst`, project format 3): the 68 MB Interview project is 1.3 MB on disk and loads faster. A legacy `.json` still reads and is replaced on the next save; older builds refuse format 3.
|
||||
* Canvas performance: the editor logs which layers each change touched, so the scene cache re-tessellates only those and keeps no copy of the document (443 → 346 MB on Interview). Zoom holds its geometry until it settles, tiny layers are skipped, vertices are 24 bytes, redraws follow vsync, the idle pointer no longer redraws, freed memory goes back to the system when idle, off-screen frame labels are skipped, and the layers panel keeps its row heights between frames (panning a large page: ~9 → ~0.7 ms a frame).
|
||||
* Frame labels: none on frames too narrow on screen, overlapping labels are dropped (selected and larger frames first), long names end in an ellipsis.
|
||||
* Dropdowns size to their longest option (capped), keep the name of `A / B / Name` options, scroll when long and show the full name on hover.
|
||||
* Settings: a Performance page (FPS, frame time, RAM, CPU, GPU, optional overlay and slow-frame badge), agent logos in the agent rows; the Chat page no longer collapses.
|
||||
|
||||
### 2026-10-25: 0.8.0 (properties modal, row merges)
|
||||
|
||||
* Properties modal (inspector, on a main: Edit properties…): a tab per property (name, kind, layer, default, delete); an options property as a Table (a row per option, a column per layer · style; colours picked from the project's variables with a swatch, visibility as a toggle, icon, image and text fields; add or remove options and columns) or as Source (each option's changes as JSON with syntax highlighting); templates from the + Property menu; Save replaces them all. The inline list is name, kind and a remove button. The `status` template writes colours the project has as variables as those variables.
|
||||
* Canvas overlays (agent glow, agent outlines, review outlines, the agent-elsewhere chip) sit below modals instead of showing through them. Two segmented controls in one row no longer share widget ids (the second did not respond).
|
||||
* `merge_variants` keeps what each moved instance shows (texts that came from its old variant), carries nested instances' property values (a badge's Tone) in the options, and takes `textLayers` for state texts only.
|
||||
|
||||
### 2026-10-25: 0.8.0 (prototype links tool, merge icon colours)
|
||||
|
||||
* `set_prototype_links`: agents can link a layer to a screen for Present mode (back arrows, rows, tabs); before, only the inspector could.
|
||||
* `merge_variants` compares an icon's colour (keeping its variable), now that icons are compared as one unit.
|
||||
|
||||
### 2026-10-25: 0.8.0 (data safety fixes)
|
||||
|
||||
* Sync no longer throws away an instance written over a nested one the way older versions stored it (its own root, no swap): it becomes a swap of the main's child at that place. Projects built before #11 lost such layers (a checked checkbox, a status badge) on the next edit of the outer main.
|
||||
* Icons: the HTML parser keeps attribute names in lowercase, so the icon and plugin code never removed the SVG's original `viewBox`; since #14 that made `<x-paper-icon>` (and icon swaps) scale twice and come out tiny. Unreleased builds only; 0.7.3 is not affected.
|
||||
* `merge_variants` refuses merges that would leave one option and changes nothing then.
|
||||
|
||||
### 2026-10-25: 0.8.0 (merge variants, pinned bars)
|
||||
|
||||
* `merge_variants`: per-state components (`Tab bar / Hoje`, `Status / Sucesso`...) become one component with an enum property. Each variant's differences from the base, matched by layer path (fills, colours, borders, weight, radius, visibility, icons; texts on request), become its option; every instance on every page moves to the base with its value; the base takes the family name; the emptied variants are deleted. A variant with layers the base lacks is left out and reported.
|
||||
* Absolute children of a scrolling screen stay pinned while its content scrolls (a tab bar, a floating button), and do not add to the scroll range.
|
||||
|
||||
### 2026-10-25: 0.8.0 (fix #22)
|
||||
|
||||
* #22: a hug column whose width comes from its parent (stretched or filled) re-measures its height once its text wraps at that width, and the parent measures again, instead of keeping a height measured while the text was narrow.
|
||||
|
||||
### 2026-10-25: 0.8.0 (component properties)
|
||||
|
||||
* Main components carry properties (`Node.props`): `text`, `bool`, `icon` and `enum`, whose options are changes in the overrides format (a layer name, `$root` or a path `Item > Label`, to a text or styles that may hold `text`, `icon` and `visibility`), so they restyle, retext, show, hide or re-icon any layer. Instances keep their values (`prop_values`) as overrides, so main edits keep them; changing the definitions re-applies every instance on every page.
|
||||
* Templates: `text` (a property per text layer), `active` (which item of a tab bar, nav or segmented row is active) and `status` (options with a colour tint the root and colour the label and icon).
|
||||
* `set_component_props`, `set_instance_props`, `props='{...}'` on `<x-paper-instance>`, properties in `get_components`, `{Prop}` in code link templates, and an inspector section (a control per property on an instance; Texts and Active item templates on a main). Icon swaps keep the layer's name and link (a `children` override); recoloured icon shapes inside instances now keep their colour after a sync.
|
||||
* Spec: `specs/component-props/spec.md`.
|
||||
|
||||
### 2026-10-25: 0.8.0 (scrolling screens)
|
||||
|
||||
* Screens keep their size and scroll what overflows: a frame's `scroll` flag (CSS `overflow: auto|scroll`, `overflow-y-auto` in the Tailwind export) makes it fixed height and clipped, and the wheel over it scrolls its content on the canvas and in Present, then pans once the content ends. The offset is view state, not saved; hit-testing and overlays follow it.
|
||||
* Toggles: a scroll button beside each screen's name (shown when it scrolls, overflows, or is hovered), Scroll overflow on a selected frame, and Screens scroll on the page for every screen at once. A fit-content screen that grew goes back to its min height.
|
||||
* Agents are told to keep screens at their size and use overflow:auto for long content.
|
||||
|
||||
### 2026-10-25: 0.8.0 (fixes #19, #20, #21)
|
||||
|
||||
* #19: a clipping frame draws its border after its children, so filled rows no longer hide it. Clipping stays rectangular: children's square corners still show past a rounded border.
|
||||
* #20: a border written with a colour variable (`1px solid var(--border)`) binds the stroke, a literal one unbinds it, and reads show `var(--name)` for bound strokes. `find_nodes` says when it stopped at 500 matches.
|
||||
* #21: hug (fit-content) frames grow around growing children (CSS flex-basis auto) instead of staying at their min height; with short content the min height stays and the growing child fills it, so a pinned footer works.
|
||||
|
||||
### 2026-10-25: 0.8.0 (follow agents, skeletons)
|
||||
|
||||
* Follow agents (Settings, Chat; on by default): in Design and Chat mode alike, the canvas pans, and switches page, to the layer any agent changes (the chat, Claude Code over MCP, a helper). Moving the canvas yourself pauses it for a moment; off, a chip offers the agent's page.
|
||||
* Skeletons: the moment the model starts writing a tool call (`ToolDraft` and `ToolInput` events from Claude Code's stream and the API), the canvas shows a shimmering skeleton over its target, or where a new artboard goes, labelled with what it is doing, until the call has run. Fresh agent content comes in through a skeleton that fades, so an outline never frames empty space.
|
||||
* Top bar: the title and the agent pill leave room measured from the buttons on the right, so nothing overlaps at narrow widths.
|
||||
|
||||
### 2026-10-25: 0.8.0 (fixes #14 to #18)
|
||||
|
||||
* #14: `write_html` fits an `<svg>` viewBox into its width and height like a browser (centred, aspect kept).
|
||||
* #15: a note on something placed straight in its artboard treats the artboard as the screen: tidy keeps cards off it instead of in its free space.
|
||||
* #16: `not-an-instance` no longer matches look-alikes with a different icon; its fix names the `<x-paper-instance>` to write (swap_instance refuses plain frames).
|
||||
* #17: instances with their own text re-measure it when the main's text style changes, so hug buttons keep fitting their labels (also covers the size reset after a swap).
|
||||
* #18: `lint_design` reports borders whose hex equals a color variable, and `fix` binds them (`stroke`), so a mode switch reaches them.
|
||||
|
||||
### 2026-10-25: 0.7.3 (QA sub-agent, helper agents, auto mode, mid-turn messages, isolated dev settings)
|
||||
|
||||
* QA sub-agent: Claude Code chats always get a Sonnet `qa` agent that lints and screenshots built frames and returns only findings, so screenshots stay out of the main context.
|
||||
* Helper agents (`src/mcp/delegate.rs`, Settings, Chat, Helper): `delegate` hands `qa`, `read` or `build` tasks to a cheaper agent CLI (pi with MiniMax M3.1 Flash, opencode, an Ollama-backed agent, any command taking `{prompt}`). It drives PaperCraft through `papercraft-cli`, runs off the UI thread, and only its last 6,000 characters come back. With a helper set, the chat agent orchestrates: keeps the brief and taste, delegates review, bulk reading and fully specified edits.
|
||||
* Dev config isolation: `PAPERCRAFT_HOME` also isolates the chat settings (`$PAPERCRAFT_HOME/.config`), so a second copy for testing never touches the installed app's settings.
|
||||
* Live check in an isolated copy: `delegate` ran the pi helper (MiniMax M3.1 Flash) as QA, a message added mid-turn went Waiting then Seen, and the agent drafted #13.
|
||||
* Fixes #10, #11, #12, #13:
|
||||
* #10: `move_nodes` to another page keeps mains' component keys and relinks every instance to moved nodes; new `rename_file` renames a project.
|
||||
* #11: swapped nested instances persist as swap overrides (`InstanceLink.swap`), preserving component identity across outer component updates and copies.
|
||||
* #12: `move_nodes` to another page takes the moved nodes' annotations along; detached notes resolve as `Detached #n`.
|
||||
* #13: `get_computed_styles` reports a text colour bound to a variable as `var(--name)`.
|
||||
* Auto mode (the composer's Auto chip): when a turn ends on `ask_user` questions, the first (recommended) option of each is sent and the chat goes on, until Stop or a turn hits its budget. The agent is told to put its recommendation first, default what only the user can know, and end each turn with next steps.
|
||||
* The Present button uses a play icon drawn like the icons beside it. Composer chips wrap instead of running under Stop and Add. Top bar: the agent pill drops its text when it would run under the buttons on the right.
|
||||
* While a chat turn works, the composer's Add (or Enter) queues a message (`src/mcp/steer.rs`). It rides on the agent's next PaperCraft tool result for that chat, so every backend reads it mid-turn; the composer shows each as Waiting or Seen. What no tool call picked up is sent as the next message when the turn ends, or goes back into the box after Stop.
|
||||
|
||||
### 2026-10-24: 0.7.2 (UI kits, code links, issues from the app, design playbook)
|
||||
|
||||
* Code links (Figma Code Connect style): a main component carries its library import and a React and an egui template (`set_code_link`). The React, Tailwind, inline JSX and egui exports write linked instances as that code, with overrides filled in and imports gathered once; the AI spec lists them as Library Components with a "use it, do not rebuild it" rule and stops describing their inner layers.
|
||||
* UI kits (`src/ui_kits.rs`, `apply_ui_kit`): shadcn/ui, Ant Design, React Bits and egui, each with Light and Dark variables, tokens and components in its look and density, all linked. The chat brief asks for the code library and starts from its kit.
|
||||
* Issues from the app (`src/issues.rs`): the agent's `report_issue` draft card (Send or Discard) and a Report a PaperCraft issue form, filed on the repository in Settings (token in the keychain, else the `tea` login), de-duplicated by title against open and closed issues.
|
||||
* design-playbook in the chat: with the `playbook` CLI installed, the brief asks whether to follow the playbook and the agent looks rules up with `design_rules`, citing ids in its report.
|
||||
* Top bar: project name and page picker on one centred row; the two-row title had outgrown the bar and covered the canvas.
|
||||
* Evidence: `src/ui_kits.rs`, `src/issues.rs`, `src/mcp/codegen.rs` (`linked_components_export_as_library_code`), `specs/ui-kits/spec.md`.
|
||||
|
||||
### 2026-10-24: 0.7.1 (rename fix)
|
||||
|
||||
* Enter, Escape and clicking away end a rename again (projects on the home screen, library assets, versions). The fields took focus back before checking whether they had lost it.
|
||||
|
||||
### 2026-10-24: 0.7.0 (images, research, plugins, fewer tokens)
|
||||
|
||||
* Images in chat: paste from the clipboard (Cmd/Ctrl+V with an image-only clipboard now reaches the composer), thumbnails in the composer and in sent messages, a lightbox, image-only messages. Attachments are sent at most 1568 px (originals kept); `zoom_image` reads a region of the original.
|
||||
* Brief and finish: `ask_user` draws questions as a card of clickable answers (palette swatches, reference image tiles, the recommended answer marked). The chat rules ask for a brief before the first design, variables and components first, sizes from the agreed density, a short report and next steps as a card.
|
||||
* Web research (the composer's Web chip): `fetch_reference_images` (public addresses only, icons, logos and backdrops dropped, social cards last), a References gallery that dims what the agent did not keep; on Claude Code a Haiku `researcher` sub-agent does the searching.
|
||||
* Image generation, opt-in (Settings, Chat, Images): Codex (offered once when signed in with a ChatGPT plan), Google Flow through `gf`, or any command (`{prompt}`, `{aspect}`, `{ratio}`, `{out}`, passed as data).
|
||||
* Plugins (Settings, Plugins): Avataaars, DiceBear, Iconify, Lorem Picsum, Flags, QR codes, placed with `<x-plugin use="…">`, cached on disk. SVG import draws masked groups as images (Avataaars).
|
||||
* Tokens: lean Claude Code sessions (42.7k to 20.6k prompt tokens), deferred tool schemas and context editing on the API, a fresh session with a recap past 120k (or on demand from the meter), per-turn cost for resumed Claude Code sessions, a 1M context meter, a turn budget. Model and effort pickers for Claude Code.
|
||||
* Also: page switcher in the Chat top bar, steps stay folded while a turn runs (no flicker), `lint_design` asks 24 px targets on desktop screens, the installer keeps `papercraft-mcp` current.
|
||||
* Evidence: `src/mcp/{refs,imagegen}.rs`, `src/plugins.rs`, `src/io/svg_import.rs`, `src/ui/{chat,chat_msgs,chat_agents,settings}.rs`, `src/chat/{anthropic,claude_code}.rs`, `tests/chat_tools.rs`, `specs/chat-tokens/spec.md`.
|
||||
|
||||
### 2026-10-22: 0.6.1 (agents via ACP, annotations)
|
||||
|
||||
* Agents are found through the login shell's PATH (read once at startup, 3 s limit, merged with the app PATH and the usual folders), so an app opened from the Dock finds Claude Code, Codex and the rest. No install instructions anywhere; a missing agent shows Not found with Set path… and Docs.
|
||||
* ACP backend (`src/chat/acp.rs`): any agent that speaks the Agent Client Protocol over stdio, with PaperCraft's MCP server, streamed updates, permission flow, cancel and load. Registry as data in `src/chat/agents.rs`: native agents found on PATH, npx agents off until enabled, custom agents by name, command and arguments. Verified here: OpenCode works; Gemini CLI's server currently refuses ACP clients; Cursor needs `cursor-agent login`; Pi works through `pi-acp` after enabling.
|
||||
* Agent panel over the chat column (Detected, Enable via npx, Not found, API endpoints, Custom, search), a Connect an agent setup card, a sign-in card, and multiple `@ Selection` chips.
|
||||
* Settings → Chat redesigned: one agents list with details, endpoints, defaults, scrolling page on one label grid.
|
||||
* Send bug: a turn that cannot start keeps the typed text and shows an error row with Retry, never a silent no-op (`a_failed_start_keeps_the_text_and_shows_an_error`).
|
||||
* Annotations: notes with leader lines on a page, six categories with keys (style comes only from the category), project and per-page templates with built-ins and a Customize modal, view modes (⌘⇧A), Annotate tool (N), side view, inspector section, Annotate with AI, six MCP tools (`annotate` batches), and comments in `get_design_spec` and the code exports. Exports and Present leave annotations out.
|
||||
* Evidence: `src/chat/{path,agents,acp}.rs`, `src/ui/{chat_agents,annotations,annotations_canvas}.rs`, `src/model/annotation.rs`, `src/editor/annotations.rs`, `src/mcp/tools.rs`, `tests/annotations*.rs`, `specs/chat-agents/`, `specs/annotations/`, `docs/chat.md`, `docs/annotations.md`.
|
||||
|
||||
### 2026-10-21: Chat mode
|
||||
|
||||
* Chat layout next to the canvas (⌘J, `Design | Chat`), with three backends: Claude Code and Codex through their CLIs (`claude -p` stream-json, `codex exec --json`) against the app's MCP server, and the Anthropic API with a key in the OS credential store. One event stream for all three; tool calls run through the same MCP dispatch, so runs, review and revert behave as before.
|
||||
* Thread: markdown replies, thinking row, tool cards, step groups, plan from `TodoWrite`, approval for destructive tools on the API backend, run checkpoints with Restore to here, usage line and context meter, `@ Selection` and image attachments, Stop.
|
||||
* Chats live in the project's `chats/` folder. Settings → Chat holds CLI paths, the default backend and the API key.
|
||||
* Offline CLI fix: an offline save writes no camera, and a top-level shape created without a position goes to the next free spot instead of the origin.
|
||||
* Evidence: `src/chat/`, `src/ui/chat.rs`, `src/ui/chat_msgs.rs`, `src/ui/chat_md.rs`, `src/ui/settings.rs`, `src/mcp/exec.rs`, `tests/fixtures/chat/`, `specs/chat/`, `docs/chat.md`.
|
||||
|
||||
### 2026-10-21: Agent runs and change review
|
||||
|
||||
* Agent editing calls group into runs, snapshot each edited page as `Before agent run N`, and end on 30 seconds idle, `finish_working_on_nodes`, or project switch. Runs persist under `runs/` and keep the newest 50.
|
||||
* Agent view lists runs and supports review, whole-run revert, per-change undo and accept all. Review adds canvas outlines and a finish toast; palette commands review or revert the last run.
|
||||
* Id-paired page diffs power review; History version compare UI remains todo. Completed with full restore (including variables, modes and tokens), tool edit classification, and wasm support.
|
||||
|
||||
### 2026-10-28: Incremental lint
|
||||
|
||||
Agents re-linted whole pages after every edit. A bounded change log (`src/editor/changes.rs`, the
|
||||
last 10000 layer changes, each with the client that made it) now records which layers every
|
||||
commit, undo and redo touched; variable edits count as touching the layers bound to them. Write
|
||||
tools return `rev`, `get_basic_info` reports it, `get_changes {since, page?, exclude?, by?}` lists
|
||||
the changes, and `lint_design {since | changedOnly}` checks only those layers plus their
|
||||
ancestors, descendants and siblings (`src/mcp/incremental.rs`), with `covered` saying what ran.
|
||||
Tests: `tests/incremental.rs`, unit tests next to the code, the attribution check in
|
||||
`tests/mcp.rs::parallel_sessions_get_their_own_runs`.
|
||||
|
||||
### 2026-10-21: Design libraries and offline CLI
|
||||
|
||||
* Variables are now collections with modes, slash groups, four types and aliases, stored in `design.json` (project version 2; 0.4.0 refuses migrated projects). Each page keeps a derived cache; every edit is one undo step per page, and the page-only removals are gone. Per-page active mode per collection.
|
||||
|
||||
@@ -12,9 +12,17 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/readme/hero.png" alt="PaperCraft with the Redesign page of its own design file open: layers on the left, the canvas in the middle, the inspector on the right" width="100%">
|
||||
<a href="docs/demo.mp4">
|
||||
<img src="docs/demo.gif" alt="PaperCraft's editor breaking apart into its panels on a 3D stage, then chat mode with an agent, annotations pinned to a sign-in screen, the variables and libraries modals, and the home screen" width="100%">
|
||||
</a>
|
||||
<br>
|
||||
<sub>PaperCraft editing its own design file (the Redesign page).</sub>
|
||||
<sub>A 40-second tour: the editor, chat mode, annotations and design systems. <a href="docs/demo.mp4">Watch it in full quality</a> (MP4, 1080p60). Rendered from the app's own design with <a href="tools/showcase/README.md">tools/showcase</a>, not screen-recorded.</sub>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/readme/hero.png" alt="PaperCraft with the Pulse habit-tracker design open: layers on the left, three annotated screens on the canvas, the inspector on the right" width="100%">
|
||||
<br>
|
||||
<sub>The finished design: three screens, a component library and annotations.</sub>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -25,6 +33,7 @@
|
||||
<a href="#keyboard-shortcuts">Shortcuts</a> ·
|
||||
<a href="#status-and-known-limits">Status</a> ·
|
||||
<a href="#project-layout">Layout</a> ·
|
||||
<a href="#contributing">Contributing</a> ·
|
||||
<a href="#license-and-credits">License</a>
|
||||
</p>
|
||||
|
||||
@@ -54,11 +63,11 @@ It is an alpha. It is good for building screens, design systems and sketches, an
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/variables.png" alt="The Variables modal listing the Colors collection with four Catppuccin modes" width="100%">
|
||||
<br><sub>Variables modal: collections, modes, groups and types.</sub>
|
||||
<img src="docs/screenshots/readme/variables.png" alt="The Variables modal listing the Colors collection with Light and Dark values for brand, foreground and background" width="100%">
|
||||
<br><sub>Variables modal: collections, modes (Light and Dark here), groups and types.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/assets.png" alt="The Assets side view with components grouped into Banner, Button, Checkbox and Chip" width="100%">
|
||||
<img src="docs/screenshots/readme/assets.png" alt="The Assets side view with the Habit row component and the shadcn Button kit" width="100%">
|
||||
<br><sub>Assets view: components grouped by name, from this file and enabled libraries.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -68,12 +77,32 @@ It is an alpha. It is good for building screens, design systems and sketches, an
|
||||
<br><sub>Libraries modal: publish this file, enable libraries from the store.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/agent-review.png" alt="Agent run review: a list of changes on the left and green, yellow and red outlines on the canvas" width="100%">
|
||||
<br><sub>Review of an agent run, with the changes outlined on the canvas.</sub>
|
||||
<img src="docs/screenshots/readme/agent-review.png" alt="The Agent side view: a finished run with Review and Revert, above the list of earlier runs" width="100%">
|
||||
<br><sub>The Agent view: runs, with Review and Revert for the latest.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/toolbar-panels.png" alt="The toolbar with the menu button folded into a back arrow, the File to Theme strip, and the floating left panel with a slim grip beside it" width="100%">
|
||||
<br><sub>Toolbar and panels: the menu button shows or hides the File / Edit strip, and the slim grip between a panel and the canvas resizes it (double click resets). <kbd>/</kbd> hides both side panels.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/menus.png" alt="The Tools menu open below its button, each tool with an icon and its shortcut" width="100%">
|
||||
<br><sub>Menus open a little below their button, are never narrower than 180 px, and carry an icon next to each item.</sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<img src="docs/screenshots/readme/annotations.png" alt="The Annotations side view listing five notes grouped by screen, with their category chips" width="100%">
|
||||
<br><sub>Annotations: rule, behaviour, content and data notes, kept with the layers they describe.</sub>
|
||||
</td>
|
||||
<td width="50%" valign="top"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
**Prototyping and review**
|
||||
* Prototype links between frames and a **Present** mode that follows them with a camera glide.
|
||||
* **Version history:** named and automatic saved versions per page; rename, restore, delete.
|
||||
@@ -90,11 +119,11 @@ Projects live in `~/Documents/papercraft/<name>/` (`project.json`, one JSON file
|
||||
|
||||
## Agents
|
||||
|
||||
PaperCraft exposes **61 tools** (`src/mcp/tools.rs`) three ways. They run the same code inside the app.
|
||||
PaperCraft exposes **98 tools** (`src/mcp/tools.rs`) three ways. They run the same code inside the app.
|
||||
|
||||
| Way | Notes |
|
||||
|---|---|
|
||||
| MCP over HTTP | Built into the app: `http://127.0.0.1:29980/mcp`. Loopback only; browser requests (with an `Origin` header) and foreign `Host` headers are rejected. `PAPERCRAFT_MCP_PORT` changes the port, `PAPERCRAFT_MCP=0` turns it off. |
|
||||
| MCP over HTTP | Built into the app: `http://127.0.0.1:29980/mcp`. Loopback only; browser requests (with an `Origin` header) and foreign `Host` headers are rejected. `?tools=core` lists 33 common tools in full plus `search_tools` and `call_tool`, which reach the rest (about a third of the schema size); lean chat agents use it. `PAPERCRAFT_MCP_PORT` changes the port, `PAPERCRAFT_MCP=0` turns it off. |
|
||||
| MCP over stdio | `papercraft-mcp` relays stdio to the running app. It lists tools even when the app is closed. |
|
||||
| Shell | `papercraft-cli` calls the same tools, so an agent with a terminal needs no schemas in its context. |
|
||||
|
||||
@@ -136,6 +165,20 @@ EOF
|
||||
<sub><code>preview.png</code> from an offline shell script, with no app running.</sub>
|
||||
</p>
|
||||
|
||||
### Chat
|
||||
|
||||
Chat mode (⌘J, or `Design | Chat` in the top bar) puts a conversation next to the canvas. It runs Claude Code and Codex through their CLIs, any agent that speaks the Agent Client Protocol (ACP, such as OpenCode, Cursor or Goose) found on your PATH, or the Anthropic API with your own key. It shows tool calls, checkpoints and run review in the thread. Chats are saved in the project's `chats/` folder. See [docs/chat.md](docs/chat.md).
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/screenshots/readme/chat.png" alt="Chat mode: the chat column on the left with its starter prompts and input, and the annotated habit-tracker screens on the canvas" width="100%">
|
||||
<br>
|
||||
<sub>Chat mode with a finished agent run: review, revert or accept from the thread.</sub>
|
||||
</p>
|
||||
|
||||
### Annotations
|
||||
|
||||
Annotations are notes that explain a design in place: business rules, behaviour, data sources, accessibility and dev notes, each a numbered card with a leader line to its layer. Categories set the look, templates say which notes a page wants (product spec, dev handoff, accessibility review and more), and the view cycles between full notes, badges and hidden with ⌘⇧A. Exports and Present mode leave them out. Agents add them in one `annotate` call. See [docs/annotations.md](docs/annotations.md).
|
||||
|
||||
### Skills
|
||||
|
||||
[`skills/`](skills) holds agent skills: `papercraft` (the build, screenshot, lint, fix loop and tool cheat sheet), `papercraft-design` (design-quality rules) and `papercraft-critique`. [docs/agents.md](docs/agents.md) is the full guide: install, registering with agents, scripts, runs and review, safety. [docs/design-system.md](docs/design-system.md) covers variables, components and libraries.
|
||||
@@ -148,12 +191,13 @@ Releases are at [git.marcospaulo.dev.br/masi/papercraft/releases](https://git.ma
|
||||
|
||||
| Build | File |
|
||||
|---|---|
|
||||
| Windows (x86_64) | `papercraft-<version>-windows-x86_64.zip`: `papercraft.exe`, `papercraft-cli.exe`, `papercraft-mcp.exe` and `papercraft-relay.exe` |
|
||||
| macOS app (Apple silicon) | `papercraft-<version>-macos-aarch64.dmg`, containing PaperCraft.app |
|
||||
| Linux (x86_64) | `papercraft-<version>-linux-x86_64.tar.gz` |
|
||||
| CLI tools, macOS | `papercraft-cli-<version>-macos-aarch64.zip` (`papercraft-cli` and `papercraft-mcp`) |
|
||||
| CLI tools, Linux | `papercraft-cli-<version>-linux-x86_64.tar.gz` |
|
||||
|
||||
macOS builds are ad-hoc signed and not notarized: on another Mac, open the app with right-click, Open the first time. Other platforms: build from source.
|
||||
The Windows zip is unsigned: SmartScreen may ask you to confirm the first run. macOS builds are ad-hoc signed and not notarized: on another Mac, open the app with right-click, Open the first time. Other platforms: build from source.
|
||||
|
||||
### Build from source
|
||||
|
||||
@@ -165,9 +209,10 @@ cargo build --release
|
||||
./target/release/papercraft ~/Documents/papercraft/Plan # open a project folder
|
||||
./target/release/papercraft --demo # sample board, not saved
|
||||
packaging/macos/package.sh # PaperCraft.app on a DMG, plus the CLI zip
|
||||
pwsh packaging/windows/package.ps1 -Arch x64 # Windows MSI, portable zip and CLI zip
|
||||
```
|
||||
|
||||
**macOS:** `xcode-select --install` for the linker. It renders with Metal through wgpu. **Linux:** install the runtime libraries (`sudo apt install libxkbcommon-x11-0 libxcb-xkb1 libvulkan1 mesa-vulkan-drivers` on Debian or Ubuntu); it runs on X11 and Wayland through Vulkan, with GL as a fallback, and file dialogs use the XDG desktop portal.
|
||||
**Windows:** the MSVC build tools (Visual Studio Build Tools with the C++ workload); it renders through wgpu on Direct3D 12 or Vulkan. **macOS:** `xcode-select --install` for the linker. It renders with Metal through wgpu. **Linux:** install the runtime libraries (`sudo apt install libxkbcommon-x11-0 libxcb-xkb1 libvulkan1 mesa-vulkan-drivers` on Debian or Ubuntu); it runs on X11 and Wayland through Vulkan, with GL as a fallback, and file dialogs use the XDG desktop portal.
|
||||
|
||||
To run a test copy next to your own PaperCraft, give it its own folder and port: `PAPERCRAFT_HOME=/tmp/pc PAPERCRAFT_MCP_PORT=29998 ./target/release/papercraft`.
|
||||
|
||||
@@ -212,24 +257,34 @@ The scenario tests drive the editor with synthetic pointer and keyboard events;
|
||||
| ⌘⌥⇧V | Variables | ⌘⌥C / ⌘⌥V | Copy / paste properties |
|
||||
| ⌘= / ⌘- / ⇧0 | Zoom in / out / 100% | ⇧1 / ⇧2 | Zoom to fit / selection |
|
||||
| PageUp / PageDown | Previous / next page | ⌘\\ | Show / hide UI |
|
||||
| ⌘J | Design / Chat mode | ⌘L | Add selection to chat (Chat mode) |
|
||||
| N | Annotate tool | ⌘⇧A | Annotations: full, badges, hidden |
|
||||
| / | Show / hide the side panels | | |
|
||||
|
||||
Draw a frame around existing shapes and it adopts them; dragging a layer into or out of a frame re-parents it. Hold ⌘ while dragging to suspend snapping.
|
||||
|
||||
## Status and known limits
|
||||
|
||||
> [!IMPORTANT]
|
||||
> PaperCraft is an alpha. [ROADMAP.md](ROADMAP.md) lists what is missing, in priority order; [PROGRESS.md](PROGRESS.md) is the milestone history.
|
||||
> PaperCraft is an alpha (0.8.x). It is used daily on Windows, macOS and Linux, and its project format migrates forward, but expect rough edges. [ROADMAP.md](ROADMAP.md) lists what is missing, in priority order; [PROGRESS.md](PROGRESS.md) is the milestone history.
|
||||
|
||||
**What works today:** local projects with pages and an infinite canvas; vector, text, auto layout, constraints, components with properties and variants, variables with modes, shared libraries and screens; annotations, lint and Present mode with prototype links; PNG, SVG and PDF export, Figma, Excalidraw and SVG import; the MCP server, CLI (online and offline) and in-app Chat mode; versions, agent runs with review and revert; optional relay sync.
|
||||
|
||||
**Known limits:**
|
||||
|
||||
* **No rotation.** Nodes are axis-aligned boxes; rotated imports are baked into paths.
|
||||
* **Text** is Inter only, with no shaping (no ligatures, complex scripts or RTL) and no font picker. IME preedit is not displayed.
|
||||
* **Rendering approximations.** Frame clipping is a rectangle, so rounded frames do not clip their corners. Overlay, dodge, burn, light, difference, exclusion and the HSL blend modes draw as Normal on the canvas (they are exact in SVG and PDF export). There is no layer or background blur yet.
|
||||
* **Variables:** gradients keep fixed colors; there are no text styles and no component variants or properties.
|
||||
* **Variables:** gradients keep fixed colors, and there are no text styles (named type styles that update every text layer).
|
||||
* **Components:** properties (text, bool, icon, enum) and `merge_variants` cover variants, but there are no variant sets with several axes, and no responsive per-breakpoint variants or breakpoint preview.
|
||||
* **Prototyping:** links and flows work in Present mode; smart transitions and hover states do not exist yet.
|
||||
* **Booleans are destructive:** the result is a new vector path with flattened curves.
|
||||
* **`write_html`** ignores margin, tables and transforms (it reports them as `ignoredStyles`) and does not fetch remote images; pass a local path or a `data:` URI.
|
||||
* **Agents:** one project is open at a time in the app, and `move_nodes` stays on one page. There is no in-app prompt box; agents run outside and connect over MCP or the CLI.
|
||||
* **Agents:** one project is open at a time in the app, and `move_nodes` stays on one page. The in-app Chat mode runs Claude Code, Codex, ACP agents or your own Anthropic key. Agents that are not in Chat connect over MCP or the CLI.
|
||||
* **Sharing** has no live presence (cursors, selections) yet.
|
||||
* **Scale:** cached frames take about 3 ms at 10k visible nodes; it is not tuned for 100k.
|
||||
* **Pen pressure** is simulated from stroke velocity on macOS tablets, because winit 0.30 does not expose it for mouse-like pen events.
|
||||
* **Releases** cover macOS (Apple silicon) and Linux x86_64. Windows packaging scripts exist but are not part of releases.
|
||||
* **Releases:** the Gitea releases ship Windows x86_64. macOS (Apple silicon) and Linux x86_64 builds come from the packaging scripts or from source; the Linux build is not part of the current releases.
|
||||
* **PDF** text is outlined, not selectable.
|
||||
|
||||
## Project layout
|
||||
@@ -264,12 +319,14 @@ analysis/ behaviour studies and design notes
|
||||
docs/ guides and screenshots
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Bug reports, ideas, docs and code are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) (setup, checks, commits, pull requests) and [RULES.md](RULES.md) (the standards a change is reviewed against). AI tools are welcome under the [AI usage policy](AI_POLICY.md), and coding agents should read [AGENTS.md](AGENTS.md). Security problems go through [SECURITY.md](SECURITY.md), and everyone follows the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
## License and credits
|
||||
|
||||
Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||||
|
||||
Bundled fonts, icons and other assets keep their own licenses, listed in [ATTRIBUTION.md](ATTRIBUTION.md); Inter is © The Inter Project Authors under the SIL Open Font License 1.1. Some algorithms are ported from MIT-licensed projects (rough.js, hachure-fill, Excalidraw), with their notices in [THIRD_PARTY.md](THIRD_PARTY.md).
|
||||
|
||||
PaperCraft started as a fork of [storytold/paper-rs](https://git.marcospaulo.dev.br/storytold/paper-rs); thanks to its contributors.
|
||||
|
||||
PaperCraft is not affiliated with Paper or Figma. Only the behavior of other tools was studied (see `analysis/`); no code, shaders, icons, fonts or assets from them are used.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
PaperCraft is an early alpha: a local-first infinite-canvas design app that agents can drive. This
|
||||
file lists what is still missing, in priority order, with an honest status. Done items are checked
|
||||
against `src/mcp/tools.rs` and the UI code (2026-10-12); rows 7, 8, 12, 15, 23, 25, 32, 34, 39, 40, 45, 46 and section 6 were updated 2026-10-21. Milestone history is in
|
||||
against `src/mcp/tools.rs` and the UI code (2026-10-12); rows 7, 8, 12, 15, 23, 25, 32, 34, 39, 40, 44, 45, 46 and section 6 were updated 2026-10-21; rows 15, 47 and 48 on 2026-10-22. Milestone history is in
|
||||
[PROGRESS.md](PROGRESS.md); known rendering and import limits are in the README.
|
||||
|
||||
Status: **done** (works today), **partial** (some of it works), **todo**.
|
||||
@@ -22,7 +22,7 @@ Agents can already build a design system through the tools; people cannot yet ma
|
||||
| 7 | Number variables (spacing, radius, font size) | **done** | Typed number, string and boolean variables in collections with groups and aliases; fields bind (size, gap, padding, radius, font size, line height, opacity, visible) in the inspector and `bind_variable`. |
|
||||
| 8 | Stroke and gradient variables | partial | Strokes and text color bind to variables. Gradients keep fixed colors. |
|
||||
| 9 | Text styles | todo | Named type styles (Heading / L, Body / M) that update every text layer using them. |
|
||||
| 10 | Component variants and properties | todo | Variants are separate components named `Button / Primary`. No variant sets, boolean or text properties. |
|
||||
| 10 | Component variants and properties | partial | Main components carry text, bool, icon and enum properties; `merge_variants` folds per-state components into one. Multi-axis variant sets todo. |
|
||||
| 11 | Swap instance, detach by tool | **done** | `swap_instance` and `detach_instance` MCP tools + UI controls in Design tab and Inspector. |
|
||||
| 12 | Cross-page and shared libraries | **done** | Instances of a main on any page; publish a project as a library, enable, update and disable it in other projects (Libraries modal, `*_library` tools). |
|
||||
| 13 | Export design tokens | **done** | `get_variables` with `format: "css"`, `"json"` (W3C DTCG + modes), or `"tailwind"` (`@theme`), plus Copy CSS / Copy JSON in Design tab. |
|
||||
@@ -32,7 +32,7 @@ Agents can already build a design system through the tools; people cannot yet ma
|
||||
|
||||
| # | Item | Status | Notes |
|
||||
|---|---|---|---|
|
||||
| 15 | MCP and CLI tool surface | **done** | 61 tools, one undo step per call, `papercraft-cli list / schema / call`. |
|
||||
| 15 | MCP and CLI tool surface | **done** | 98 tools, one undo step per call, `papercraft-cli list / schema / call`. |
|
||||
| 16 | Agent skills | **done** | `skills/papercraft` and `skills/papercraft-design`. |
|
||||
| 17 | Accessibility annotations | todo | Reading order, roles and alt text on layers, checked by the linter. |
|
||||
| 18 | Code export from frames | **done** | `get_jsx` supports `format: "inline"`, `"tailwind"` (Tailwind utility classes), and `"html"` (HTML + CSS rules with `:root` variables). |
|
||||
@@ -63,7 +63,7 @@ Agents can already build a design system through the tools; people cannot yet ma
|
||||
| 32 | Version history | **done** | Named and automatic saved versions per page: rename, restore, delete (`src/versions.rs`). Restore now includes variables and tokens; compare todo. |
|
||||
| 33 | Figma import | **done** | `.fig` binary (Kiwi wire decoder + zip container) and Figma REST API JSON import in `src/io/fig.rs`. |
|
||||
| 34 | Multiplayer and cloud sync | partial | Optional shared projects through the relay, with merge of concurrent edits to one page. Presence (cursors, selections) todo. Stays optional. |
|
||||
| 35 | Release builds for Windows and Linux | todo | The code targets them (wgpu, winit); packaging today is the macOS DMG and the `papercraft-cli` zip. |
|
||||
| 35 | Release builds for Windows and Linux | partial | Windows x86_64 zips are released and `packaging/windows` builds an MSI; Linux x86_64 is not in the current releases. |
|
||||
|
||||
## 5. Quality
|
||||
|
||||
@@ -84,5 +84,13 @@ Spec: [specs/agent-runs/spec.md](specs/agent-runs/spec.md), tasks: [specs/agent-
|
||||
| 41 | Agent presence on the canvas | todo | Outline the node a tool call touches, label it, pause or stop the run. |
|
||||
| 42 | `get_context` tool | todo | Selection, variables, nearby components and lint summary in one call, so an agent orients in one round trip. |
|
||||
| 43 | Comments assigned to agents | todo | `@agent` in a thread triggers an edit and a reply. Needs threaded comments. |
|
||||
| 44 | In-app prompt bar | todo | Run a prompt on the selection through the user's own key or local CLI. Largest scope: provider config, key storage, cost. |
|
||||
| 44 | In-app prompt bar | **done** | Chat mode: Claude Code, Codex or your own Anthropic key; see `docs/chat.md`. |
|
||||
| 45 | Correct tool annotations | **done** | `lint_design` no longer sends `readOnlyHint`; `fix: true` edits are classified. |
|
||||
|
||||
## 7. Annotations and chat agents
|
||||
|
||||
| # | Item | Status | Notes |
|
||||
|---|---|---|---|
|
||||
| 47 | Design annotations | **done** | Notes with leader lines and categories, templates per project and page, view modes (⌘⇧A), Annotate tool (N), side view, inspector section, six MCP tools; see `docs/annotations.md`. |
|
||||
| 48 | ACP agents in Chat | **done** | Any ACP agent on the PATH (OpenCode works; Cursor needs `cursor-agent login`), npx agents after enabling, custom agents; see `docs/chat.md`. |
|
||||
| 48b | Free agents | **done** | OpenCode and Pi on free models with `Free (auto)` rotation, Pi provider keys, an install dialog per OS; see `specs/free-agents`. |
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# Project rules
|
||||
|
||||
The standards a change to PaperCraft is reviewed against. [CONTRIBUTING.md](CONTRIBUTING.md) says
|
||||
*how* to contribute; this file says what "good" means once you do.
|
||||
|
||||
## Code
|
||||
|
||||
* **Immutability by default.** Return new values rather than mutating shared ones. The document
|
||||
changes only through `Editor::edit` transactions, so every change is one undo step and is
|
||||
logged for the renderer's cache.
|
||||
* **Small, cohesive files.** 200 to 400 lines is typical, 800 is the ceiling. Split a module
|
||||
when it grows past that. Organise by feature, not by type.
|
||||
* **Small functions** (under about 50 lines) and **shallow nesting** (4 levels at most); prefer
|
||||
early returns.
|
||||
* **Handle errors explicitly.** No `unwrap` or `expect` on user data, file contents or network
|
||||
responses; return an error with context. User-facing messages are plain language. Never swallow
|
||||
an error silently.
|
||||
* **Validate at the boundaries**: MCP and CLI arguments, imported files (Figma, Excalidraw, SVG),
|
||||
project folders and anything from the relay. Treat all of it as untrusted.
|
||||
* **No magic numbers.** Name thresholds, sizes and delays as constants; UI measures come from
|
||||
the theme, not literals.
|
||||
* **No dead code, debug prints or commented-out blocks.**
|
||||
* **Match the surrounding code**: naming, comment density and idiom. Comments say *why*, not
|
||||
*what*.
|
||||
* **Cross-platform.** Code must build on Windows, macOS and Linux and for the web build. Gate
|
||||
platform code with `cfg`, never assume a path separator, and keep
|
||||
`cargo check --lib --target wasm32-unknown-unknown` passing.
|
||||
* **No new heavy dependencies** without discussion. Licenses must be compatible with
|
||||
MIT OR Apache-2.0.
|
||||
|
||||
## Data safety
|
||||
|
||||
PaperCraft stores people's work. Treat that as the top priority.
|
||||
|
||||
* Writes to projects are **atomic** (`src/io/atomic.rs`); never write a page in place.
|
||||
* A format change bumps the project format version and ships a **migration**. Older data must
|
||||
keep loading; if a newer build writes something an older build cannot read, the older build
|
||||
must refuse it rather than corrupt it.
|
||||
* Agent and sync paths must never silently drop a layer. If a merge cannot keep something, it
|
||||
says so.
|
||||
* Destructive operations are undoable or confirmed.
|
||||
|
||||
## The machine you build on
|
||||
|
||||
A full build links dozens of test binaries and can take every core and most of the memory of a
|
||||
desktop. The person is still using that machine, often with PaperCraft open.
|
||||
|
||||
* **Look before you start a heavy job.** Check free memory, CPU load and what is already running
|
||||
(the person's app, another build, other agents) before a build, a test run or clippy. If memory
|
||||
is short or another build is running, wait or ask.
|
||||
* **Leave room.** Cap Cargo at no more than half the logical cores (`-j 8` on a 16-core machine,
|
||||
or `CARGO_BUILD_JOBS`) and the test harness too (`-- --test-threads=8`); go lower when memory is
|
||||
tight, since linking is the peak. Run heavy jobs at low priority (`nice` on Unix, Below Normal
|
||||
on Windows).
|
||||
* **One heavy job at a time.** Never run two builds side by side; each target folder is a full
|
||||
build's worth of compiling and linking. Parallel agents share one build slot.
|
||||
* **Run only what your change touches**: `cargo check` before `cargo build`, and the test
|
||||
targets that cover the change (`cargo test --test incremental`). The full suite runs on GitHub
|
||||
before a release (`.github/workflows/test.yml`), not on a desktop.
|
||||
* **Stop at once when the person says the machine is slow**, then check that no compiler, linker
|
||||
or test process of yours is left over.
|
||||
* **Clean up.** Delete scratch target folders and worktrees when the work is merged; they grow to
|
||||
tens of gigabytes.
|
||||
|
||||
## UI
|
||||
|
||||
* **Use the shared kit.** Controls, spacing, radii and colours come from `craft-ui`,
|
||||
`src/ui/kit.rs` and the theme, not one-off values.
|
||||
* **Themes are modes.** Never branch on a theme to pick a colour; read it from the palette.
|
||||
* **Nothing overflows.** Text truncates with an ellipsis, rows keep their controls inside, and
|
||||
panels step aside when the window is narrow. Test a narrow window and a long name.
|
||||
* **Targets are at least 44 px** where a person taps or clicks something primary, and everything
|
||||
stays reachable from the keyboard.
|
||||
* **Animation is quick**: eased and under about 200 ms.
|
||||
* **Look at it.** Run the app or take a headless screenshot at scale 2 in each theme before
|
||||
asking for review.
|
||||
|
||||
## MCP tools and the CLI
|
||||
|
||||
* Every tool has a precise one-paragraph description, a JSON schema, and a test in
|
||||
`tests/mcp.rs`. Descriptions are what agents read; write them for that reader.
|
||||
* One tool call is **one undo step**.
|
||||
* Names follow the existing style (`verb_noun`); reuse Paper-compatible names and argument shapes
|
||||
where a tool has an equivalent.
|
||||
* Reads are cheap and bounded. Apply limits, say when you truncated, and never dump the whole
|
||||
document by default.
|
||||
* The server listens on `127.0.0.1` only and rejects browser origins. Do not weaken that.
|
||||
* Update the tool count in the README and ROADMAP when you add or remove tools.
|
||||
|
||||
## Designs made with PaperCraft
|
||||
|
||||
These apply to example projects, templates, screenshots and skill content, and are what the
|
||||
skills teach agents.
|
||||
|
||||
* **Themes are modes, never copies.** One artboard per screen. Give each variable a value per
|
||||
mode and switch with `set_variable_mode`. Do not duplicate artboards as light and dark
|
||||
versions, and do not rebuild a screen with other hex values.
|
||||
* **Every colour on a themed screen is a variable** (`var(--name)` on fills and text,
|
||||
`bind_variable` for strokes, opacity and visibility).
|
||||
* **No overflow.** Nothing extends past its artboard or clipping parent. Scrolling content is
|
||||
clipped inside its viewport and ends above floating bars with a fade and enough bottom padding.
|
||||
Check each artboard programmatically *and* by screenshot in each mode.
|
||||
* Run `lint_design` and fix what it finds: contrast, the 4 px grid, type scale and 44 px targets.
|
||||
* Release design locks when you are done so the next person can inspect.
|
||||
|
||||
## Skills and docs
|
||||
|
||||
* Skills in [skills/](skills) are instructions for agents: keep them short, concrete and tested
|
||||
against a real agent run.
|
||||
* Docs describe what exists. Do not document a feature that is not built; put plans in `specs/`
|
||||
and `ROADMAP.md`.
|
||||
* Screenshots come from the real app, in a generic project (no personal data, local paths or
|
||||
real names), at scale 2.
|
||||
|
||||
## Branding
|
||||
|
||||
The tree carries no third-party product branding; `.debrand` lists the forbidden patterns and
|
||||
the check runs in review. Describe PaperCraft on its own terms.
|
||||
|
||||
## Review
|
||||
|
||||
A pull request is approved when it has no unresolved **critical** (security, data loss) or
|
||||
**high** (bug, significant quality) issues. Medium and low comments are suggestions the author
|
||||
may take or decline with a reason.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Security policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
PaperCraft is an alpha. Security fixes go into the latest release and `main`.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
**Do not open a public issue.** Use GitHub's private reporting: the repository's **Security** tab,
|
||||
then **Report a vulnerability**. Include what you found, how to reproduce it, the version and
|
||||
platform, and the impact you see.
|
||||
|
||||
A maintainer will acknowledge the report as soon as they can and keep you updated until it is
|
||||
fixed. Please give us reasonable time to ship a fix before you disclose it.
|
||||
|
||||
## What counts
|
||||
|
||||
Examples of in-scope problems:
|
||||
|
||||
* the MCP server (`127.0.0.1:29980`) or the relay accepting requests it should reject (browser
|
||||
origins, DNS rebinding, another user);
|
||||
* path traversal or code execution from a project folder, a design-system or library package, or
|
||||
an imported Figma, Excalidraw or SVG file;
|
||||
* the updater or the agent integrations running something the person did not approve;
|
||||
* secrets (an Anthropic key, relay tokens) written to disk unprotected or sent anywhere
|
||||
unexpected.
|
||||
|
||||
PaperCraft is local-first: it opens no network port beyond the loopback MCP server unless you
|
||||
share a project through a relay you run.
|
||||
@@ -62,3 +62,15 @@ SOFTWARE.
|
||||
|
||||
Inter 4.1 by Rasmus Andersson — SIL Open Font License 1.1 (`assets/fonts/OFL.txt`). Regular,
|
||||
Medium, SemiBold, Bold, Italic and Bold Italic are bundled unmodified.
|
||||
|
||||
Interface fonts, SIL Open Font License 1.1, Latin subsets as served by Google Fonts, unmodified
|
||||
(Regular, Medium, SemiBold): Geist (Vercel), Figtree (Erik Kennedy), DM Sans (Colophon Foundry,
|
||||
Jonny Pinhorn), Nunito (Vernon Adams, Jacques Le Bailly), Quicksand (Andrew Paglinawan),
|
||||
Fredoka (Milena Brandão, Hafontia), and Varela Round (Joe Prince; Regular only).
|
||||
|
||||
## Agent logos
|
||||
|
||||
`assets/agents/*.svg` are from LobeHub Icons (`@lobehub/icons-static-svg` 1.80.0,
|
||||
https://github.com/lobehub/lobe-icons), MIT License, Copyright (c) LobeHub. They are used
|
||||
unmodified to identify each coding agent in the agent picker. The marks are trademarks of
|
||||
their owners.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Cline</title><path d="M17.035 3.991c2.75 0 4.98 2.24 4.98 5.003v1.667l1.45 2.896a1.01 1.01 0 01-.002.909l-1.448 2.864v1.668c0 2.762-2.23 5.002-4.98 5.002H7.074c-2.751 0-4.98-2.24-4.98-5.002V17.33l-1.48-2.855a1.01 1.01 0 01-.003-.927l1.482-2.887V8.994c0-2.763 2.23-5.003 4.98-5.003h9.962zM8.265 9.6a2.274 2.274 0 00-2.274 2.274v4.042a2.274 2.274 0 004.547 0v-4.042A2.274 2.274 0 008.265 9.6zm7.326 0a2.274 2.274 0 00-2.274 2.274v4.042a2.274 2.274 0 104.548 0v-4.042A2.274 2.274 0 0015.59 9.6z"></path><path d="M12.054 5.558a2.779 2.779 0 100-5.558 2.779 2.779 0 000 5.558z"></path></svg>
|
||||
|
After Width: | Height: | Size: 750 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Cursor</title><path d="M22.106 5.68L12.5.135a.998.998 0 00-.998 0L1.893 5.68a.84.84 0 00-.419.726v11.186c0 .3.16.577.42.727l9.607 5.547a.999.999 0 00.998 0l9.608-5.547a.84.84 0 00.42-.727V6.407a.84.84 0 00-.42-.726zm-.603 1.176L12.228 22.92c-.063.108-.228.064-.228-.061V12.34a.59.59 0 00-.295-.51l-9.11-5.26c-.107-.062-.063-.228.062-.228h18.55c.264 0 .428.286.296.514z"></path></svg>
|
||||
|
After Width: | Height: | Size: 547 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>GithubCopilot</title><path d="M19.245 5.364c1.322 1.36 1.877 3.216 2.11 5.817.622 0 1.2.135 1.592.654l.73.964c.21.278.323.61.323.955v2.62c0 .339-.173.669-.453.868C20.239 19.602 16.157 21.5 12 21.5c-4.6 0-9.205-2.583-11.547-4.258-.28-.2-.452-.53-.453-.868v-2.62c0-.345.113-.679.321-.956l.73-.963c.392-.517.974-.654 1.593-.654l.029-.297c.25-2.446.81-4.213 2.082-5.52 2.461-2.54 5.71-2.851 7.146-2.864h.198c1.436.013 4.685.323 7.146 2.864zm-7.244 4.328c-.284 0-.613.016-.962.05-.123.447-.305.85-.57 1.108-1.05 1.023-2.316 1.18-2.994 1.18-.638 0-1.306-.13-1.851-.464-.516.165-1.012.403-1.044.996a65.882 65.882 0 00-.063 2.884l-.002.48c-.002.563-.005 1.126-.013 1.69.002.326.204.63.51.765 2.482 1.102 4.83 1.657 6.99 1.657 2.156 0 4.504-.555 6.985-1.657a.854.854 0 00.51-.766c.03-1.682.006-3.372-.076-5.053-.031-.596-.528-.83-1.046-.996-.546.333-1.212.464-1.85.464-.677 0-1.942-.157-2.993-1.18-.266-.258-.447-.661-.57-1.108-.32-.032-.64-.049-.96-.05zm-2.525 4.013c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zm5 0c.539 0 .976.426.976.95v1.753c0 .525-.437.95-.976.95a.964.964 0 01-.976-.95v-1.752c0-.525.437-.951.976-.951zM7.635 5.087c-1.05.102-1.935.438-2.385.906-.975 1.037-.765 3.668-.21 4.224.405.394 1.17.657 1.995.657h.09c.649-.013 1.785-.176 2.73-1.11.435-.41.705-1.433.675-2.47-.03-.834-.27-1.52-.63-1.813-.39-.336-1.275-.482-2.265-.394zm6.465.394c-.36.292-.6.98-.63 1.813-.03 1.037.24 2.06.675 2.47.968.957 2.136 1.104 2.776 1.11h.044c.825 0 1.59-.263 1.995-.657.555-.556.765-3.187-.21-4.224-.45-.468-1.335-.804-2.385-.906-.99-.088-1.875.058-2.265.394zM12 7.615c-.24 0-.525.015-.84.044.03.16.045.336.06.526l-.001.159a2.94 2.94 0 01-.014.25c.225-.022.425-.027.612-.028h.366c.187 0 .387.006.612.028-.015-.146-.015-.277-.015-.409.015-.19.03-.365.06-.526a9.29 9.29 0 00-.84-.044z"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Goose</title><path d="M21.595 23.61c1.167-.254 2.405-.944 2.405-.944l-2.167-1.784a12.124 12.124 0 01-2.695-3.131 12.127 12.127 0 00-3.97-4.049l-.794-.462a1.115 1.115 0 01-.488-.815.844.844 0 01.154-.575c.413-.582 2.548-3.115 2.94-3.44.503-.416 1.065-.762 1.586-1.159.074-.056.148-.112.221-.17.003-.002.007-.004.009-.007.167-.131.325-.272.45-.438.453-.524.563-.988.59-1.193-.061-.197-.244-.639-.753-1.148.319.02.705.272 1.056.569.235-.376.481-.773.727-1.171.165-.266-.08-.465-.086-.471h-.001V3.22c-.007-.007-.206-.25-.471-.086-.567.35-1.134.702-1.639 1.021 0 0-.597-.012-1.305.599a2.464 2.464 0 00-.438.45l-.007.009c-.058.072-.114.147-.17.221-.397.521-.743 1.083-1.16 1.587-.323.391-2.857 2.526-3.44 2.94a.842.842 0 01-.574.153 1.115 1.115 0 01-.815-.488l-.462-.794a12.123 12.123 0 00-4.049-3.97 12.133 12.133 0 01-3.13-2.695L1.332 0S.643 1.238.39 2.405c.352.428 1.27 1.49 2.34 2.302C1.58 4.167.73 3.75.06 3.4c-.103.765-.063 1.92.043 2.816.726.317 1.961.806 3.219 1.066-1.006.236-2.11.278-2.961.262.15.554.358 1.119.64 1.688.119.263.25.52.39.77.452.125 2.222.383 3.164.171l-2.51.897a27.776 27.776 0 002.544 2.726c2.031-1.092 2.494-1.241 4.018-2.238-2.467 2.008-3.108 2.828-3.8 3.67l-.483.678c-.25.351-.469.725-.65 1.117-.61 1.31-1.47 4.1-1.47 4.1-.154.486.202.842.674.674 0 0 2.79-.861 4.1-1.47.392-.182.766-.4 1.118-.65l.677-.483c.227-.187.453-.37.701-.586 0 0 1.705 2.02 3.458 3.349l.896-2.511c-.211.942.046 2.712.17 3.163.252.142.509.272.772.392.569.28 1.134.49 1.688.64-.016-.853.026-1.956.261-2.962.26 1.258.75 2.493 1.067 3.219.895.106 2.051.146 2.816.043a73.87 73.87 0 01-1.308-2.67c.811 1.07 1.874 1.988 2.302 2.34h-.001z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kimi</title><path d="M21.846 0a1.923 1.923 0 110 3.846H20.15a.226.226 0 01-.227-.226V1.923C19.923.861 20.784 0 21.846 0z"></path><path d="M11.065 11.199l7.257-7.2c.137-.136.06-.41-.116-.41H14.3a.164.164 0 00-.117.051l-7.82 7.756c-.122.12-.302.013-.302-.179V3.82c0-.127-.083-.23-.185-.23H3.186c-.103 0-.186.103-.186.23V19.77c0 .128.083.23.186.23h2.69c.103 0 .186-.102.186-.23v-3.25c0-.069.025-.135.069-.178l2.424-2.406a.158.158 0 01.205-.023l6.484 4.772a7.677 7.677 0 003.453 1.283c.108.012.2-.095.2-.23v-3.06c0-.117-.07-.212-.164-.227a5.028 5.028 0 01-2.027-.807l-5.613-4.064c-.117-.078-.132-.279-.028-.381z"></path></svg>
|
||||
|
After Width: | Height: | Size: 786 B |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Mistral</title><path clip-rule="evenodd" d="M3.428 3.4h3.429v3.428h3.429v3.429h-.002 3.431V6.828h3.427V3.4h3.43v13.714H24v3.429H13.714v-3.428h-3.428v-3.429h-3.43v3.428h3.43v3.429H0v-3.429h3.428V3.4zm10.286 13.715h3.428v-3.429h-3.427v3.429z"></path></svg>
|
||||
|
After Width: | Height: | Size: 418 B |
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
||||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Tencent</title><path d="M9.976 1L24 9.8l-10.587.015L10.723 23H5.489L8.18 9.8H3.244L1 5.4h8.077L9.976 1z"></path></svg>
|
||||
|
After Width: | Height: | Size: 282 B |
@@ -6,7 +6,7 @@ FROM rust:slim AS build
|
||||
# Must match the wasm-bindgen crate in Cargo.lock.
|
||||
ARG WASM_BINDGEN=0.2.129
|
||||
RUN rustup target add wasm32-unknown-unknown \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends binaryen pkg-config libssl-dev \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends binaryen pkg-config libssl-dev libdbus-1-dev \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& cargo install wasm-bindgen-cli --version ${WASM_BINDGEN} --locked
|
||||
COPY --from=vendor crates /vendor
|
||||
@@ -21,7 +21,7 @@ COPY . .
|
||||
RUN sh web/build.sh && cargo build --release --locked --bin papercraft-relay
|
||||
|
||||
FROM debian:stable-slim
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends libssl3 ca-certificates \
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends libssl3 libdbus-1-3 ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --system --uid 10001 papercraft
|
||||
COPY --from=build /src/target/release/papercraft-relay /usr/local/bin/papercraft-relay
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# Driving PaperCraft with agents
|
||||
|
||||
PaperCraft exposes one set of 61 tools three ways. They all run the same code inside the app, so an agent sees what you see and every call is one undo step.
|
||||
PaperCraft exposes one set of 88 tools three ways. They all run the same code inside the app, so an agent sees what you see and every call is one undo step.
|
||||
|
||||
| Way | Use it when | Needs the app running |
|
||||
|---|---|---|
|
||||
| MCP over HTTP, `http://127.0.0.1:29980/mcp` | the agent client speaks MCP (Claude Code, Codex, pi) | yes |
|
||||
| MCP over stdio, `papercraft-mcp` | the client only starts stdio servers | for calls; `tools/list` works offline |
|
||||
| Shell, `papercraft-cli` | the agent has a terminal and you want no tool schemas in its context | for `call`; `list`, `schema` and `icons` work offline |
|
||||
| Shell, `papercraft-cli` | the agent has a terminal and you want no tool schemas in its context | for `call`; `list`, `schema` and `icons` work offline (with the app running, `list` and `schema` show the app's tools) |
|
||||
|
||||
If you would rather talk to the agent inside the app, Chat mode (⌘J, or `Design | Chat` in the top bar) is the in-app alternative. It runs Claude Code or Codex through their CLIs against the same MCP server, ACP agents such as OpenCode or Gemini CLI, or the Anthropic API with your own key, and keeps the conversation next to the canvas, with the same runs, review and revert. The setup and the thread are in [chat.md](chat.md). The rest of this guide covers driving the app from your own terminal or agent client.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -89,6 +91,8 @@ papercraft-cli call get_screenshot '{"nodeId":"abc123:1"}' --project Demo -o car
|
||||
|
||||
### Shell and scripts
|
||||
|
||||
When `call` or `shell` reaches the app and the app is another version, the CLI prints once on stderr, for example `papercraft-cli 0.6.1 ≠ app 0.6.2; reinstall the CLI`. `list` and `schema` read the running app's tool list, so tools newer than the CLI still show; with the app closed they use the CLI's built-in list.
|
||||
|
||||
`papercraft-cli shell [--project X] [--offline]` keeps one session open (offline, one in-process project; otherwise the app). A line is `<tool> <json>` (JSON may span lines until its braces close) or a command: `:help :tools :schema <tool> :open <project> :pages :page <name|id> :shot [nodeId] [file] :undo :redo :save :watch on|off :quit`. `:watch on` renders the artboard last edited to `preview.png` after each editing call, shown inline on iTerm2, WezTerm and kitty. Input is plain stdin (no line editing or history; use `rlwrap` for that). Piped input prints no prompt and exits 1 if any line failed, listing the line numbers on stderr.
|
||||
|
||||
```sh
|
||||
@@ -104,12 +108,42 @@ write_html {
|
||||
EOF
|
||||
```
|
||||
|
||||
## Checking only what changed
|
||||
|
||||
On a big page a full `lint_design` after every edit repeats the same output, and a `get_node_info` call per layer is slow (about 53 s for 397 layers). Check the change, then check everything once.
|
||||
|
||||
* Every write tool result includes `rev`, the document revision after the call. `get_basic_info` reports the current `rev`. Reads never change it.
|
||||
* `get_changes {since, page?}` returns `{rev, since, truncated, changes: [{page, ids}]}`: what the edits since `since` touched. `truncated: true` means the log no longer reaches that far back, so do a full pass.
|
||||
* `lint_design {changedOnly: true}` lints since this client's last lint; `{since: rev}` lints since a revision you name. A scoped lint checks the touched layers plus their ancestors, descendants, siblings and every layer bound to a changed variable, and reports its coverage in `covered`, for example `{checked: 12, total: 397, since: 40, partial: true}`. Plain `lint_design` is a full pass (`partial: false`). The `overflow` rule flags a layer that extends past its parent or clipping artboard (scroll frames are excluded).
|
||||
* `get_bounds {nodeIds?, pageId?, depth?}` returns `columns` `[id, parent, x, y, w, h, absX, absY, clip]` and one row per layer for a subtree in one call (at most 2000 rows; then `truncated` and `continueWith`, the nodeIds for the next call). Use it instead of looping `get_node_info`.
|
||||
* Always finish with one full `lint_design` (no arguments) and a screenshot per artboard in each theme mode before reporting. A partial lint speeds up the loop; it does not replace that pass.
|
||||
|
||||
In the app, read-only calls no longer draw per-layer outlines or keep the "Working" pill lit for long. The agent workflow, with an example, is in [incremental-checks.md](../skills/papercraft/references/incremental-checks.md).
|
||||
|
||||
## Design-system tools
|
||||
|
||||
* Variables: `get_variables` and `set_variables` take collections, groups (`color/brand/primary`), types (`color`, `number`, `string`, `boolean`), `mode`, `alias`, `description` and `hidden`; the old `{"name":"#hex"}` call still works and lands in the first collection. `create_collection` makes a collection with modes, `delete_variables` removes by name (refuses aliased ones unless `force`), `bind_variable` binds or detaches a field of layers (`stroke`, `text-color`, `width`, `height`, `gap`, `padding`, `radius`, `font-size`, `line-height`, `opacity`, `visible`).
|
||||
* Components: `get_components` lists mains of every page and enabled library with a `componentKey`; `create_instance` and `swap_instance` accept a main from any page or library (`componentId` or `componentKey`).
|
||||
* Libraries: `publish_library`, `list_libraries`, `enable_library`, `update_library`, `disable_library`. They work offline too, on the project store.
|
||||
|
||||
## Seeing what the user sees
|
||||
|
||||
* `get_basic_info` and `get_selection` include `viewport: {pageId, rect: [x, y, w, h], zoom}`: the page the user is viewing and the world rect on screen.
|
||||
* `set_viewport {pageId?, nodeIds? | rect?, zoom?, padding?, force?}` switches the user's page (as the page list does) and fits the nodes or rect, or centres them at an explicit `zoom` (0.01 to 256; `select_nodes {zoom: true}` stops at 100%). While the user is panning or zooming (10 s after their last camera move) it is refused with "user is moving the canvas", unless `force: true`.
|
||||
* `find_nodes {textValue}` finds text layers containing the phrase on every page (or `pageId`), ignoring case and line breaks; `*` wildcards match the whole value instead. `get_tree_summary {nodeId, fullText: true}` prints every text layer's complete text, for copy review across screens.
|
||||
* `get_screenshot {nodeId, annotations: true}` draws the notes on the captured layers (cards, number badges, leader lines), so an agent can check note placement.
|
||||
|
||||
## Annotation tools
|
||||
|
||||
* `annotate {pageId?, notes: [{node, text, type?}]}` adds many notes in one undo step. `node` is a node id or an exact layer name; the first line of `text` is the label and the rest the body; `type` is a category key (default `rule`) that the page's template must enable. Placement is automatic. `annotate {tidy: true, frame?}` with no notes re-lays-out the existing notes and returns `{"tidied": n}`, the number of notes it moved.
|
||||
* `get_annotation_template {pageId?}` returns the allowed types with their guidance and fields. Call it once before `annotate`.
|
||||
* `set_annotation_template {template, scope?: "page" | "project", pageId?}` switches the template by id or name.
|
||||
* `upsert_annotation_template {template: {id?, name, display?, categories: [{key, enabled?, guidance?, fields?}]}, categories?: [{key, name, color, icon}], setAs?: "project" | "page", pageId?}` creates or replaces a template and its category styles in one undo step. `delete_annotation_template {template}` removes one (the last stays).
|
||||
* `get_annotations {nodeId?, pageId?, full?}` reads notes as `[number, node, type, text]` rows.
|
||||
* `update_annotation {id | ref, text?, type?}` and `delete_annotations {ids}` take ids or references such as `Sign in #3`.
|
||||
|
||||
See [annotations.md](annotations.md) for categories, templates and what the notes look like.
|
||||
|
||||
## Safety
|
||||
|
||||
The server listens on loopback only, rejects requests that carry an `Origin` header (browsers) and foreign `Host` headers. Anything on your machine that can reach `127.0.0.1:29980` can edit the open project, so do not forward the port. Undo (⌘Z, or the `undo` tool) reverts any agent edit.
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Annotations
|
||||
|
||||
An annotation is a note that explains a design in place: a business rule, a behaviour, where the data comes from, an accessibility gap. Each note is a small card with a number, a category, a label and an optional body, joined to its layer by a leader line. Teams use them for review and handoff, and turn them off for presenting.
|
||||
|
||||
Annotations live on a page and are saved with it (the `annotations` list in the page file). Pages without any load and save exactly as before. Categories and templates are project-wide and live in `design.json`.
|
||||
|
||||
## Categories
|
||||
|
||||
Every note has one category. The category is the only thing that sets how a note looks: its icon, colour and badge come from the category, and a note stores none of them. Change a category's colour or icon and every note in it changes.
|
||||
|
||||
Each category has a short type key. Agents and the Annotate popover use the key; the UI shows the name.
|
||||
|
||||
| Key | Name | Icon | Colour | Use for |
|
||||
|---|---|---|---|---|
|
||||
| `rule` | Business rule | scale | violet `#7c5cff` | the rule or constraint the part enforces |
|
||||
| `behaviour` | Behaviour | zap | blue `#2f80ed` | states, transitions, edge cases |
|
||||
| `content` | Content | type | green `#1fa87a` | copy, tone, length |
|
||||
| `data` | Data | database | amber `#e08a1e` | where a value comes from |
|
||||
| `a11y` | Accessibility | accessibility | pink `#d94f8a` | focus, labels, contrast, reading order |
|
||||
| `dev` | Dev note | code | grey `#6b7280` | implementation notes for engineers |
|
||||
|
||||
You can add categories, rename them, change their key, colour and icon, and write guidance for each one in the Customize modal. A custom category gets a key too.
|
||||
|
||||
## Templates
|
||||
|
||||
A template says which kinds of notes a page wants, and what each note should contain. The project has a default template; a page can use its own instead.
|
||||
|
||||
Built-in templates are editable copies, not locked. Each category in a template has enabled or not, guidance (a short prompt for what the note should say) and optional fields (structured lines such as `Owner`).
|
||||
|
||||
| Template | Categories | Fields |
|
||||
|---|---|---|
|
||||
| Product spec | rule, behaviour, content, data | none |
|
||||
| Dev handoff | behaviour, data, dev, a11y | none |
|
||||
| Accessibility review | a11y | Issue, WCAG, Fix |
|
||||
| Content review | content | Tone, Max length |
|
||||
| Minimal | rule | none |
|
||||
|
||||
Pick the project default or "This page" from the template chips at the top of the Annotations side view. Built-ins can be reset; custom templates can be deleted. Duplicate copies any template so you can change it.
|
||||
|
||||
**Customize…** (side view) opens the template editor: name, display (full notes or badges), the enabled categories, the guidance and fields of each. Changes to categories apply to the whole project.
|
||||
|
||||
The Annotate popover offers only the enabled categories of the active template, and shows each field as a labelled line. A field is stored as a `Name: value` line at the top of the note's body. Agents get the same rule: `annotate` refuses a type the template disables and lists the allowed keys.
|
||||
|
||||
## View modes
|
||||
|
||||
Three levels, all view state. They are not saved in the design and not undoable.
|
||||
|
||||
* **Full**: every note shows its label and the first lines of its body. Below 60% zoom notes show as badges, so cards never spill over the design; zoom in to read them.
|
||||
* **Badges**: only the number badges are shown.
|
||||
* **Hidden**: nothing is drawn.
|
||||
|
||||
`⌘⇧A` (View → Annotations) cycles Full → Badges → Hidden → Full. The starting mode follows the template's display setting until you change it.
|
||||
|
||||
Two finer controls:
|
||||
|
||||
* **Per note**: collapse a note to its number badge; click the badge to expand it again.
|
||||
* **Per category**: show or hide a category from the category filter in the side view.
|
||||
|
||||
Annotations are drawn as an overlay on the canvas only. Exports (PNG, SVG, PDF) and Present mode never include them. There is no option to include them in an export yet.
|
||||
|
||||
## Annotate tool
|
||||
|
||||
Press `N` for the Annotate tool, then click a layer. The New annotation popover opens beside it with:
|
||||
|
||||
* Category, from the template's enabled categories.
|
||||
* Label, up to 40 characters. The popover shows the count.
|
||||
* The template's fields, if any.
|
||||
* A note, in Markdown.
|
||||
|
||||
Save creates the note with its leader line to the layer's side; Cancel discards it. You can also select layers, right-click, and choose **Annotate**.
|
||||
|
||||
Each note's leader line starts at the note card and ends at the anchor on the target. The card and the anchor are stored relative to the target, so moving or resizing the layer keeps the note attached. Drag a card to move it; the line re-routes.
|
||||
|
||||
If the target is deleted, the note stays where it was in the side view's **Detached** group, with a warning badge.
|
||||
|
||||
## Annotations side view
|
||||
|
||||
The side view lists the page's notes:
|
||||
|
||||
* Grouped by top-level frame, plus **Page** (notes on no frame) and **Detached**.
|
||||
* Each row shows the number, the category chip and the label. Click a row to select the layer and zoom to it.
|
||||
* The category filter shows or hides categories.
|
||||
* Edit a note inline, or delete it. Edits are one undo step each.
|
||||
* **Add annotation** opens the popover for the current selection.
|
||||
* **Annotate with AI** puts a prompt in the Chat composer, unsent, with the selected frame (or the page) as a chip. The prompt asks the agent to read the template with `get_annotation_template` and add all the notes in one `annotate` call. It needs a saved project and Chat mode.
|
||||
|
||||
Numbers run per frame in reading order (top to bottom, then left to right), so a frame reads 1, 2, 3. Notes on no frame are numbered after that, from 1.
|
||||
|
||||
## Inspector
|
||||
|
||||
When a single layer is selected, the inspector has an Annotations section. It lists that layer's notes, with edit and delete, and an Add annotation button. The note's anchor is one of the four sides of the target (Top, Right, Bottom, Left) with a position along it.
|
||||
|
||||
## Agents
|
||||
|
||||
Agents work with notes through six MCP tools, all in `src/mcp/tools.rs`. They are one undo step per call and appear in `edits_doc`, so a run that only adds notes still gets a Review card (a "Notes" group, counted as added, changed or removed), and Revert removes them.
|
||||
|
||||
| Tool | What it does |
|
||||
|---|---|
|
||||
| `get_annotation_template` | Reads the template: `[type key, guidance, field names]` for each enabled type. Call it once before `annotate`. |
|
||||
| `set_annotation_template` | Switches the template by id or name, for one page (default) or the project (`scope: "project"`). |
|
||||
| `upsert_annotation_template` | Creates or replaces a template, and creates or restyles categories in the same call (`categories: [{key, name, color, icon}]`). `setAs: "project"` or `"page"` makes it the default or the page override. Returns `{id, name, types}`. |
|
||||
| `delete_annotation_template` | Deletes a template by id or name. The last template stays. |
|
||||
| `annotate` | Adds many notes at once. Each note names a node id or an exact layer name on the page, a `text` and a `type` (default `rule`). |
|
||||
| `get_annotations` | Reads notes as `[number, nodeName, type, text]` rows (text capped at 120 characters). `full: true` returns complete objects; `nodeId` limits to a subtree; `pageId` picks another page. |
|
||||
| `update_annotation` | Changes the text or type of one note, by `id` or by reference such as `Sign in #3`. |
|
||||
| `delete_annotations` | Deletes notes by ids or references such as `Sign in #3`. |
|
||||
|
||||
Custom template with a new category, used on the current page:
|
||||
|
||||
```json
|
||||
{
|
||||
"template": {"name": "Legal review", "categories": [
|
||||
{"key": "legal", "guidance": "Legal risk this part carries.", "fields": ["Owner", "Law"]},
|
||||
{"key": "rule"}]},
|
||||
"categories": [{"key": "legal", "name": "Legal", "color": "#aa3355", "icon": "scale"}],
|
||||
"setAs": "page"
|
||||
}
|
||||
```
|
||||
|
||||
`icon` must be one of the built-in icon names (an unknown name lists them). Every key in the template must exist, either already or in `categories`. The template and category edit is one undo step; `setAs: "page"` adds a second.
|
||||
|
||||
For `annotate` and `update_annotation`, the first line of `text` is the label and the rest is the body. There are no positions, icons or colours: PaperCraft places each note beside its target, outside the frame, stacked so none overlap. `annotate` is all or nothing: if one note is refused (an unknown layer, or a type the template disables), none is added and the error lists the allowed keys.
|
||||
|
||||
Batched call, on a page whose template is Product spec:
|
||||
|
||||
```json
|
||||
{
|
||||
"notes": [
|
||||
{"node": "Sign in", "type": "rule", "text": "Lock after 5 failed tries\nLocks for 15 minutes, then resets."},
|
||||
{"node": "Email field", "type": "data", "text": "Source: users.email\nValidated on blur"},
|
||||
{"node": "Submit", "type": "behaviour", "text": "Disabled until both fields are valid\nSpinner while the request runs"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The reply is one row per note, `[number, node, type, label]`. Then `get_annotations {}` reads them back, and `delete_annotations {"ids":["Sign in #3"]}` removes one by its frame reference.
|
||||
|
||||
`annotate {"tidy": true}` with no notes re-runs placement for every note on the page (or on `frame`) in one undo step and returns `{"tidied": n}`, the number of notes it moved; `0` means the layout was already tidy.
|
||||
|
||||
To see the notes, `get_screenshot {"nodeId": "<frame>", "annotations": true}` draws the cards, number badges and leader lines of the notes whose target is inside the captured layers, placed and routed as on the canvas (full cards, scale 1). The image grows to include the cards. Without `annotations`, or with `false`, the screenshot is the same as before.
|
||||
|
||||
**Handoff.** `get_design_spec` and the code exports (`get_jsx`) include the notes of the frame as comments. Each note is one line, `// 1 Business rule: Lock after 5 failed tries`, with its body lines as more `//` lines, so code generated from a frame carries the rules next to the markup.
|
||||
|
||||
## Storage
|
||||
|
||||
* The page file has an `annotations` list. Each note holds its target (or a point, when it has none), its anchor, label, body, category, position, author (`user` or `agent`) and creation time. A note whose target is gone is marked `detached`.
|
||||
* `design.json` holds the categories and the templates, with the project default. A page may name its own template.
|
||||
* Old projects load unchanged. A page without annotations saves byte-identical to before.
|
||||
@@ -0,0 +1,177 @@
|
||||
# Chat mode
|
||||
|
||||
Chat mode puts a conversation with an agent next to the canvas. Press ⌘J, or use the `Design | Chat` switch in the top bar, to turn it on. The chat column sits on the left (420 to 640 px, 480 by default, resizable) and the live canvas fills the rest. The layers and inspector are hidden; the inspector can be opened as a narrow overlay on the canvas's right edge. Design mode works as before, and a chat keeps running in the background when you switch back.
|
||||
|
||||
The mode is remembered per project. Entering a project that was last left in Chat mode opens in Chat mode and shows its newest chat.
|
||||
|
||||
Chat mode is native only. The web build shows the switch disabled with a note.
|
||||
|
||||
## Backends
|
||||
|
||||
Chat drives the same tools as an agent connected over MCP, so the edits, runs, undo and revert behave the same whichever backend you use. There are three ways in: two CLIs that PaperCraft talks to directly, any agent that speaks ACP, and API endpoints.
|
||||
|
||||
**Claude Code** runs `claude -p` with streaming JSON in and out. PaperCraft writes an MCP config that points the CLI at the app's own server on `127.0.0.1`, allows the `mcp__papercraft__*` tools, and passes `--strict-mcp-config`, so the CLI sees only PaperCraft's server. The PaperCraft skill text goes in through `--append-system-prompt-file`. The working directory is the project folder. Follow-up messages resume the CLI session by its id, so the agent keeps its context.
|
||||
|
||||
**Codex** runs `codex exec --json` in the project folder. The PaperCraft server is added with a `-c mcp_servers.papercraft.url=...` override, and its tools are pre-approved (`default_tools_approval_mode="approve"`), so Codex does not stop to ask for each one. The PaperCraft instructions are prepended to the first prompt of a chat; resumed sessions do not repeat them. Follow-ups resume the Codex session the same way.
|
||||
|
||||
**ACP agents** run any agent that speaks the Agent Client Protocol (https://agentclientprotocol.com) over stdio. PaperCraft starts the agent once per turn, sends `initialize` and `session/new` with the project folder as the working directory, and passes PaperCraft's MCP server: over HTTP when the agent supports it, otherwise through the bundled `papercraft-mcp` stdio bridge. The prompt goes out as `session/prompt`, and the agent's streamed updates become the same thread as the other backends: text, thinking, tool cards and the plan. Stop sends `session/cancel`. Follow-ups resume with `session/load` when the agent supports it. ACP has no system prompt field, so the PaperCraft instructions go in front of the first prompt.
|
||||
|
||||
The ACP agents PaperCraft knows about, and what was checked on the development Mac:
|
||||
|
||||
* **OpenCode** (`opencode acp`): works. Its free models (`opencode/*-free`, Big Pickle) need no key.
|
||||
* **Cursor** (`cursor-agent acp`): sign in first with `cursor-agent login`.
|
||||
* **Pi** (`pi-acp`, an adapter run through `npx`): enable it once in the agent panel or Settings. `pi-acp` does not pass MCP servers on to Pi, so a lean Pi gets its own folder (`PI_CODING_AGENT_DIR`) whose only package is `pi-mcp-adapter`, connected to PaperCraft. Pi runs on the providers you add a key for in Settings → Chat → Pi: OpenRouter (`:free` models, 50 requests a day without credits), Groq, Gemini, NVIDIA NIM and Cerebras. Keys live in the OS secret store and reach Pi only as environment variables.
|
||||
* **Goose, Kimi, Devin, Junie, Mistral Vibe, Hermes, OpenHands, Stakpak, VT Code, Poolside, Droid**: started with the command their own docs give. They are not all verified here.
|
||||
* **Claude Code and Codex ACP adapters** (`npx`): listed under Enable via npx. Claude Code and Codex themselves use the direct backends above.
|
||||
|
||||
Agents that run through `npx` are off until you enable them, because enabling runs code that npx fetches from npm.
|
||||
|
||||
**Free models.** For OpenCode and Pi the model list starts on free models (`Free only`) and offers `Free (auto)`, the default for new chats on them. It starts on the free model with the largest context. When a model fails with a rate limit, quota, overload or "gone" error before any tool ran, the turn moves to the next free one (at most two more) and the thread says so. A model that failed is skipped for 15 minutes. A failure after a tool ran is not retried, because the design may already have changed.
|
||||
|
||||
ACP permissions: PaperCraft's own tools run without asking, except the destructive ones listed under Approval below. Any other tool the agent asks to run (its own shell or file tools, for example) shows an approval card with Approve or Deny in the thread, and Deny is sent back to the agent.
|
||||
|
||||
**Custom agents** are your own ACP agents: a name, a command (a program name on the resolved `PATH`, or a full path) and arguments. The arguments are split on spaces, with no quoting. Custom agents appear under Custom in the agent panel and in Settings → Chat.
|
||||
|
||||
**API endpoints** call a model over HTTPS with your own key. Settings → Chat holds a list of endpoints, each with a name, a format, a base URL, a model and an optional key. The app's tool list is sent as the API tools, and every tool call runs through the same code path as an MCP call on the UI thread. The loop continues until the model ends its turn, for up to 40 tool rounds per message.
|
||||
|
||||
* **Format**: `Anthropic` is the Messages API (`POST {base}/v1/messages`, `x-api-key`). `OpenAI` is Chat Completions (`POST {base}/chat/completions`, `Authorization: Bearer`, streaming `choices[].delta`, tools as `{"type":"function",...}`, tool results as `tool` messages). Both give the same thread: text, tool cards, approvals, Stop, the usage line.
|
||||
* **Base URL and model** are free text, for example `https://api.anthropic.com`, any Anthropic-compatible URL, or `http://127.0.0.1:11434/v1`. The key is optional, so local servers need none.
|
||||
* A default endpoint named `Anthropic` (`https://api.anthropic.com`, `claude-opus-5-5`) exists from the start, so the first run needs only a key.
|
||||
* The agent panel lists Detected agents, API endpoints and Custom agents (see Setup). Each chat remembers its backend.
|
||||
* **Claude-only fields** (effort, the thinking summary, the refusal fallback and its beta header, eager tool-input streaming, prompt-cache breakpoints) are sent only when the base URL host is `api.anthropic.com`. For that host the model menu offers `claude-opus-5-5`, `claude-sonnet-5-5` and `claude-haiku-5-5`, effort is `medium` by default (`low` and `high` in the menu), and the Thinking row shows the summary. Every other endpoint gets a plain request. Missing `usage`, thinking or cache fields in the reply are fine.
|
||||
* **OpenAI format and images**: attached images are sent as `image_url` data URIs. A tool message in this format carries text only, so a tool result with an image (such as `get_screenshot`) goes back as the text `[image omitted]`, and the image follows as an extra user message in the same round.
|
||||
* **Usage line**: input, output and cached tokens, as the server reports them; nothing when it reports none. A cost estimate shows only for the Claude models in the small price table in `src/chat/endpoint.rs`. It is not a bill.
|
||||
* `ANTHROPIC_API_KEY` in the environment is used for `api.anthropic.com` when no key is stored. `PAPERCRAFT_ANTHROPIC_BASE_URL` points Anthropic-format requests at another URL (for tests and proxies).
|
||||
|
||||
## Setup
|
||||
|
||||
### Finding agents
|
||||
|
||||
Chat finds agents the way your terminal does. At startup PaperCraft reads your login shell's `PATH` once, off the UI thread (`$SHELL -l`, 3 s limit), and merges it with the app's own `PATH` and the usual folders (`~/.local/bin`, `/opt/homebrew/bin`, `/usr/local/bin`, `~/.cargo/bin`, `~/.bun/bin`, `~/.npm-global/bin`, Volta and nvm). Detection and the agent processes both use that merged `PATH`. This is why an agent installed under `~/.local/bin` or Homebrew is found even when PaperCraft was opened from the Dock.
|
||||
|
||||
A row shows a version when the agent reports one. A program with the right name that is not the agent (for example the `goose` database migration tool, which shares its name with Block's Goose agent) shows `Wrong program` with its path, not a false Ready.
|
||||
|
||||
PaperCraft never shows install commands. An agent that is not found shows `Not found`, with `Set path…` (opens Settings) and `Docs` (the agent's own documentation). Install the agent with its own installer, then press Detect again.
|
||||
|
||||
### Agent panel
|
||||
|
||||
Click the agent chip in the chat header. The panel opens over the chat column with a shadow and a dimmed backdrop, sized like the column. It has a search field (name or id), and these sections:
|
||||
|
||||
* **Detected**: agents found on your `PATH`, with the version and path. Click one to use it for this chat.
|
||||
* **Enable via npx**: agents that start through `npx` (Pi, Qwen Code, Kilo, Cline, Auggie, Copilot CLI, Qoder, CodeBuddy and the Claude Code and Codex ACP adapters). Each row has an `Enable` chip. The section says that it runs code fetched by npx.
|
||||
* **Not found**: registry agents that are not on this computer, folded by default.
|
||||
* **API endpoints**: your endpoints, with an `Add` action that opens Settings.
|
||||
* **Custom**: `Add custom agent` opens Settings.
|
||||
|
||||
Each row has a status chip: `Ready`, `Enable`, `Not found`, `Wrong program`, `No key` (an endpoint without a key) or `Set up` (an endpoint without a URL or model). Arrow keys move through the rows, Enter picks one, Escape closes the panel.
|
||||
|
||||
Settings → Chat has the same list, so an agent can also be hidden from the panel there (the `Show in the chat agent picker` switch on its row).
|
||||
|
||||
### Setup card
|
||||
|
||||
A chat with no backend chosen shows one card, "Connect an agent". It lists the agents detected on this computer, up to four, with `Show all agents` for the rest. Below them are `Add an API endpoint` and `Add custom agent`. When OpenCode or Pi is not found, its row has `Install…`. The dialog offers the install methods that work on this OS (Homebrew, Scoop, npm, Nix or the agent's install script; on Linux also the distribution's own). It shows the exact command and runs it only after you click Install, streaming the output. Methods that need `sudo` or an admin shell, and npm when Node is missing or too old, are shown with the reason and a command to copy instead.
|
||||
|
||||
### Sign-in
|
||||
|
||||
When an agent says it needs you to sign in (ACP's "Authentication required" and similar messages), the failed turn shows "<Agent> needs you to sign in" with the command from the agent's docs, then Retry. For Cursor that is `cursor-agent login`, for OpenCode `opencode auth login`, for Codex `codex login`, for Claude Code's ACP adapter `claude /login`. For agents without a known command the card says to sign in with the agent's own app and retry. Chat does not sign in for you.
|
||||
|
||||
### Settings → Chat
|
||||
|
||||
The page scrolls and keeps one grid for its labels. Its sections:
|
||||
|
||||
* **Agents**: one row per agent, with its status, version and path. A details disclosure holds the **Path override** (for a binary that is not on `PATH`, or a specific install; empty means the first match on the merged `PATH`), the **Arguments override** (replaces the registry's ACP arguments, for example `--acp --debug`), and for npx agents the **Enable** button. The **Show in the chat agent picker** switch hides an agent from the panel. Custom agents have Name, Command and Arguments fields and a Remove button. `Detect again` re-runs detection.
|
||||
* **API endpoints**: add, edit and remove endpoints (name, format, base URL, model) and set the default one. Each has a key field with Save and Remove. Keys are stored in the macOS Keychain, Windows Credential Manager or the Linux Secret Service, under the service name `PaperCraft` and the user `api-key:<base URL>`. An older single key (`anthropic-api-key`) is still read for `api.anthropic.com` and moves to the new entry the next time you save it. Removing a key makes that endpoint stop until a key is saved again (for `api.anthropic.com`; other endpoints work without one).
|
||||
* **Defaults for new chats**: the backend, the endpoint, and the effort for `api.anthropic.com`.
|
||||
|
||||
Settings from before endpoints existed (`api_model`) move into the default endpoint on first load. The `claude_path` and `codex_path` settings from earlier versions still win for their agent until you save the Agents list. These defaults, the agent settings and the endpoint list (never the keys) are saved in `chat_settings.json` in your config folder (`~/Library/Application Support/papercraft/` on macOS). They are not part of any project.
|
||||
|
||||
## The thread
|
||||
|
||||
The header has the chat title (rename), the chat list (new chat, switch, delete), the agent chip (for example `Claude Code · Opus 5.5`) and the context meter. The meter shows the tokens in the context at the last model call against a 200k window, and turns amber past 75%. It is hidden for Codex, which reports no per-call usage.
|
||||
|
||||
Replies are markdown: paragraphs, lists, code blocks with a copy button, inline code and links. While a turn runs, the reply streams in.
|
||||
|
||||
* **Thinking**: when the backend reports reasoning, a row shows "Thinking…" while it runs and collapses to a summary when it ends. Expand it to read the text.
|
||||
* **Tool cards**: one card per tool call with the verb and target (for example "Drawing layout · Card"), a spinner while it runs, and a failed state with the error. Args and result are collapsible. `get_screenshot` results show a thumbnail.
|
||||
* **Step groups**: consecutive tool calls in one reply fold into one line ("Built the card · 5 steps") that expands to the cards.
|
||||
* **Plan**: when the agent keeps a todo list (Claude Code's `TodoWrite`, or an ACP plan), the latest list is pinned above the composer with a "done of total" count.
|
||||
* **Approval**: on the API backend and for ACP agents, the destructive tools (`delete_pages`, `disable_library`, `import_design_system`, and `delete_nodes` on more than 20 layers) wait for Approve or Deny before they run. Deny returns an error to the model, which can then carry on. For ACP agents, any non-PaperCraft tool the agent asks for shows the same card. The Claude Code and Codex CLIs do not show this card. They use the CLI's own permission rules, and in non-interactive mode a tool that needs permission is denied. The papercraft tools are pre-allowed on both CLIs.
|
||||
* **Checkpoint**: when an agent run starts, the thread shows "Checkpoint · before run N". "Restore to here" reverts that run and every run after it, through the same revert as the Agent view.
|
||||
* **Run card**: when a run ends, "Agent finished, N changes" with Review, Revert and Accept. These are the same actions as the run review on the canvas. The card names the client that made the run ("Chat", "Claude Code · 2") and refreshes after an undo.
|
||||
* **Progress**: while a run is open, the step group and the Working pill count calls and touched layers ("Styling · 212 calls · 1,340 changes"). The pill clears when the turn ends.
|
||||
* **Errors**: a failed turn shows a red row with Retry. A sign-in problem shows the sign-in card (see Setup).
|
||||
* **Canvas follow**: when a tool call targets a node, the canvas pans to it, unless you moved the camera in the last 10 seconds.
|
||||
* **Agent presence**: after each agent call, the layers it pointed at or changed get an accent outline that fades over 3 seconds, and the newest carries a label with the verb and the client ("Styling · Claude Code"). This works for chat turns and for any MCP client. When the agent works on another page, a chip "Agent on page X →" switches to it.
|
||||
|
||||
## Design-system context
|
||||
|
||||
Every turn carries a summary of the project's design system, so the agent reuses what exists instead of drawing look-alikes:
|
||||
|
||||
* variables by collection and group with their modes (colors as `name=#light/#dark`), tokens as scales;
|
||||
* main components on every page and in enabled libraries, as `componentKey · name · w×h · uses`, most used first;
|
||||
* enabled libraries and their update status, and the page's annotation template;
|
||||
* a reuse rule: place components with `create_instance` / `swap_instance`, colors as `var(--…)`, spacing on tokens.
|
||||
|
||||
The summary is capped at about 1,500 tokens. A large system keeps its most used components and collapses variable groups to counts, with a pointer to `get_components` / `get_variables` for the rest. It is rebuilt only when the design system changes. The API backends get it as a separate system block (cached on `api.anthropic.com`); CLI and ACP agents get it in their first prompt, and again as `[Design system changed] …` on the next message after a change. The context meter includes it. Turn it off in Settings → Chat → **Send design-system summary**.
|
||||
|
||||
`lint_design` has a matching rule, `not-an-instance`: a frame built by hand that matches a component (same structure, size within 4 px, same fill, radius and stroke) is flagged with the component to use.
|
||||
|
||||
## Pointing at the canvas
|
||||
|
||||
* **Pick**: the Pick chip next to `@ Selection` turns the canvas into a picker without changing your selection. Click a layer to add it, or Shift+drag an area; Esc cancels. Each pick types `@Name` at the composer cursor and adds a chip.
|
||||
* **⌥-click**: in Chat mode, ⌥-click a layer on the canvas to add it the same way.
|
||||
* **Area chips** are sent as `[Area] page P, rect x,y,w,h, contains: <ids…>` (the top layers wholly inside the area, up to 40).
|
||||
* **Crops**: layer, area and `@ Selection` chips carry an image of that part of the page with its notes drawn (1024 px max). The API backend gets it as an image block; CLI and ACP agents get a file under `.papercraft-chat-tmp/`.
|
||||
* **Ask (⌘K)**: with a selection, ⌘K opens a prompt box anchored above it. Enter sends to the chat with the selection chip and its crop, and the reply streams in the chat column without leaving Design mode. With nothing selected, ⌘K opens the command palette (also ⌘⇧/).
|
||||
|
||||
## Actions on a selection
|
||||
|
||||
The **Actions** menu opens from the canvas right-click menu, from the Actions chip in the composer and from the command palette ("Actions: …"). It works in Design and Chat modes.
|
||||
|
||||
Instant actions run in the app with no model. Each one is a single undo step and its own run (client "Actions"), and a toast reports what it did with Review and Revert:
|
||||
|
||||
* **Fix lint here**: `lint_design {fix: true}` on the selection.
|
||||
* **Bind to tokens**: binds hard-coded colors to the nearest variable (within 12 RGB) and gaps, padding and radii to the nearest spacing or radius token (within 2 px). Values with no close match are listed.
|
||||
* **Use components**: replaces each `not-an-instance` hit with an instance of its component in the same place, keeping the texts.
|
||||
* **Rename layers**: names default layers (`Frame 12`, `Text`) from their text or role.
|
||||
* **Annotate**: opens the annotation popover.
|
||||
|
||||
Agent actions send a prompt with a selection chip to the chat and switch to Chat mode: **Optimize** (tighten layout and spacing to the token scale, fix contrast, keep the content, prefer components), **Make consistent with the design system**, **Write states** (empty, loading, error), **Variations ×3** and **Annotate with AI**.
|
||||
|
||||
**Send open notes to chat**, in the inspector's Annotations section for a top-level frame, sends the frame's open feedback and its `todo` / `question` notes as one prompt with a chip.
|
||||
|
||||
## Composer and attachments
|
||||
|
||||
Type in the composer, ⏎ sends and ⇧⏎ adds a line. The empty state offers suggestions built from the project, such as "Design a sign-in screen using this project's variables" or "Lint this page and fix warnings".
|
||||
|
||||
* **Selection**: the `@ Selection` chip adds the current selection to the message as a chip (also ⌘L). Each click adds another chip, labelled with the layer name or `<first> · N layers`; an identical selection is added once, and each chip is removed on its own. Every chip is sent as a short line with the layer name and node ids.
|
||||
* **Start failures**: if a turn cannot start (agent missing, bad path), an error row with Retry shows in the thread and your text stays in the composer.
|
||||
* **Images**: the image button opens a file picker. You can also paste a clipboard image or drop image files on the column (PNG, JPEG, GIF, WebP).
|
||||
|
||||
For the API backend, images are sent as image blocks, so the model sees them. For the CLIs and ACP agents, each image is saved under `.papercraft-chat-tmp/` in the project and its path is added to the prompt for the agent to read.
|
||||
|
||||
While a turn runs, the send button becomes **Stop**. Stop asks the backend to interrupt: the CLIs get an interrupt, or SIGINT, then a kill after 3 seconds; ACP agents get `session/cancel`, then a kill if they do not exit. Every backend ends within 3 seconds. The thread shows a muted "Stopped" line, not an error. Tools that already ran stay applied, and the partial run can be reviewed or reverted like any other.
|
||||
|
||||
## Storage
|
||||
|
||||
A chat is one file in the project: `chats/<id>.json`, holding the title, the backend (kind, model, effort, CLI or ACP session id, agent) and the messages with their tool calls, usage and run ids. The API backend also keeps `chats/<id>.api.json`, the raw messages array (Messages format with thinking blocks, or Chat Completions format, whichever the endpoint speaks), so a follow-up replays exactly. The chat records its endpoint id. Deleting a chat asks first and removes both files, and its saved images.
|
||||
|
||||
Projects without a `chats/` folder load unchanged. The newest chat opens when you enter Chat mode.
|
||||
|
||||
## Privacy
|
||||
|
||||
PaperCraft never writes an API key into a project file, a chat or a log. The key lives in the OS credential store. The CLIs and ACP agents run with your own logins and do not get the API keys from the environment: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `OPENAI_API_KEY` and `CODEX_API_KEY` are removed from their environment, so Claude Code and Codex bill your account, not a key you set for PaperCraft. The MCP server listens on loopback only, as it does for any other client.
|
||||
|
||||
Prompts, project content and tool results go to whichever backend you chose: the CLI's or agent's model service, or the base URL of the endpoint you picked. Nothing goes to any other service. An npx agent downloads its package from npm the first time it runs. If a server echoes your key in an error message, the app hides it before showing the message.
|
||||
|
||||
## Limits
|
||||
|
||||
* One turn per project at a time. Sending while a turn runs is refused with "A turn is already running"; use Stop first. Switching to another project stops a running turn.
|
||||
* The web build has no Chat mode.
|
||||
* While the agent works, the canvas shows fading outlines of the layers it touches; the full change review (added, changed, removed) comes after the turn, when the run is reviewed.
|
||||
* Variations ×3 is a prompt for now: the agent makes the variants; there is no side-by-side Keep tag yet.
|
||||
* Over the ACP stdio bridge the chat id does not reach the server, so those runs show under the agent's client name and close after 30 seconds idle instead of at turn end.
|
||||
* The API backend stops after 40 tool rounds in one message and says so.
|
||||
* Codex reports no per-call usage, so the context meter is hidden for it.
|
||||
* Agents need to be installed and signed in with their own tools. Chat does not install them or sign them in for you.
|
||||
* ACP terminal and file-system requests from an agent are answered as not supported. Agents use their own file tools.
|
||||
|
After Width: | Height: | Size: 14 MiB |
@@ -60,7 +60,7 @@ papercraft-cli call set_text_content '{"updates":[{"nodeId":"<label layer id fro
|
||||
* `create_instance` returns the instance id and its layers, so you can set text and styles on them. Changes made through `set_text_content` or `update_styles` on an instance are recorded as overrides; everything else follows the main component.
|
||||
* In `write_html`, `<x-paper-instance component-id="<id>" layer-name="Button / Save"/>` places an instance inline.
|
||||
* `get_components` lists the main components of every page (and library components) with `componentKey`, page and how many instances each has.
|
||||
* **Across pages.** Instances can be placed from a main on any page: `create_instance` and `swap_instance` take a `componentId` (`pageId:num`) or a `componentKey` (stable, from `get_components`). Editing a main on page A updates its instances on every other page, one undo step per page. If the main is deleted its instances stay as they are, marked "Main missing" in Assets, where you can detach them. `import_components` still copies a main onto a page, but reuse no longer needs it.
|
||||
* **Across pages.** Instances can be placed from a main on any page: `create_instance` and `swap_instance` take a `componentId` (`pageId:num`) or a `componentKey` (stable, from `get_components`). Editing a main on page A updates its instances on every other page, one undo step per page. If the main is deleted its instances stay as they are, marked "Main missing" in Assets, where you can detach them. `<x-paper-instance component="Name"/>` in `write_html` links the same way when the main is on another page (it used to copy the main onto the page). `import_components` still copies mains onto a page, detached from the source; it is for moving a design system, not for using components.
|
||||
* **Limits.** No variants or properties: model variants as separate components (`Button / Primary`, `Button / Secondary`). `detach_instance` and `swap_instance` exist as tools; in the editor detach with ⌘⌥B.
|
||||
|
||||
## Icons
|
||||
@@ -93,6 +93,8 @@ Fill-based sets (Phosphor, Material) follow variables; Lucide is stroke-based an
|
||||
| `layer-name` | warning | default names (Frame, Rectangle, Group...) on layers with children |
|
||||
| `hard-coded-color` | warning, info | a literal color that equals a variable (warning), or a literal used repeatedly when variables exist (info) |
|
||||
|
||||
| `overflow` | error | a layer extending past its artboard or clipping parent (by how many px, on which side), or text taller than its box. Scrolling frames and layers pinned in them are exempt. Never auto-fixed by `fix: true`: resize, move or let the parent grow. `get_bounds` returns every rect in one call |
|
||||
|
||||
Instances are skipped (their main component is linted). The rules are conventions, not law: pass `scale` to match your own type scale and ignore `info` items you disagree with. Lint does not judge taste; always look at `get_screenshot` too.
|
||||
|
||||
## Libraries
|
||||
|
||||
|
Before Width: | Height: | Size: 700 KiB After Width: | Height: | Size: 720 KiB |
|
Before Width: | Height: | Size: 619 KiB After Width: | Height: | Size: 675 KiB |
|
Before Width: | Height: | Size: 593 KiB After Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 624 KiB After Width: | Height: | Size: 638 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 824 KiB After Width: | Height: | Size: 838 KiB |
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 698 KiB After Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 872 KiB After Width: | Height: | Size: 885 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 356 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 416 KiB |
@@ -59,7 +59,7 @@ function Get-PeHeader([string] $Path) {
|
||||
return @{ Machine = [BitConverter]::ToUInt16($bytes, $pe + 4); Subsystem = [BitConverter]::ToUInt16($bytes, $pe + 0x5C) }
|
||||
}
|
||||
$Machine = switch ($Arch) { 'x64' { 0x8664 } 'x86' { 0x14C } 'arm64' { 0xAA64 } }
|
||||
foreach ($name in @('papercraft.exe', 'papercraft-cli.exe', 'papercraft-mcp.exe')) {
|
||||
foreach ($name in @('papercraft.exe', 'papercraft-cli.exe', 'papercraft-mcp.exe', 'papercraft-relay.exe')) {
|
||||
$exePath = Join-Path $Bin $name
|
||||
if (Test-Path $exePath) {
|
||||
$h = Get-PeHeader $exePath
|
||||
@@ -71,13 +71,13 @@ foreach ($name in @('papercraft.exe', 'papercraft-cli.exe', 'papercraft-mcp.exe'
|
||||
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
||||
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
||||
Copy-Item (Join-Path $Bin 'papercraft.exe'), (Join-Path $Bin 'papercraft-cli.exe'), (Join-Path $Bin 'papercraft-mcp.exe') $Stage
|
||||
Copy-Item (Join-Path $Bin 'papercraft.exe'), (Join-Path $Bin 'papercraft-cli.exe'), (Join-Path $Bin 'papercraft-mcp.exe'), (Join-Path $Bin 'papercraft-relay.exe') $Stage
|
||||
foreach ($f in 'LICENSE-MIT', 'LICENSE-APACHE', 'ATTRIBUTION.md', 'README.md') {
|
||||
$p = Join-Path $Root $f
|
||||
if (Test-Path $p) { Copy-Item $p $Stage }
|
||||
}
|
||||
|
||||
& (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'papercraft.exe') (Join-Path $Stage 'papercraft-cli.exe') (Join-Path $Stage 'papercraft-mcp.exe')
|
||||
& (Join-Path $PSScriptRoot 'sign.ps1') (Join-Path $Stage 'papercraft.exe') (Join-Path $Stage 'papercraft-cli.exe') (Join-Path $Stage 'papercraft-mcp.exe') (Join-Path $Stage 'papercraft-relay.exe')
|
||||
|
||||
# ---- MSI ---------------------------------------------------------------------------------------
|
||||
$Msi = Join-Path $Dist "papercraft-$Version-windows-$Arch.msi"
|
||||
@@ -119,9 +119,22 @@ $CliZip = Join-Path $Dist "papercraft-cli-$Version-windows-$Arch.zip"
|
||||
Remove-Item -Force $CliZip -ErrorAction SilentlyContinue
|
||||
Compress-Archive -Path $CliPortable -DestinationPath $CliZip
|
||||
|
||||
# ---- relay zip ---------------------------------------------------------------------------------
|
||||
$RelayPortable = Join-Path $TargetDir "windows-package\papercraft-relay-$Version-windows-$Arch"
|
||||
Remove-Item -Recurse -Force $RelayPortable -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $RelayPortable | Out-Null
|
||||
Copy-Item (Join-Path $Stage 'papercraft-relay.exe') $RelayPortable
|
||||
foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE', 'ATTRIBUTION.md') {
|
||||
$p = Join-Path $Root $f
|
||||
if (Test-Path $p) { Copy-Item $p $RelayPortable }
|
||||
}
|
||||
$RelayZip = Join-Path $Dist "papercraft-relay-$Version-windows-$Arch.zip"
|
||||
Remove-Item -Force $RelayZip -ErrorAction SilentlyContinue
|
||||
Compress-Archive -Path $RelayPortable -DestinationPath $RelayZip
|
||||
|
||||
# Smoke test
|
||||
Invoke-Native 'papercraft-cli --help' { & (Join-Path $Stage 'papercraft-cli.exe') --help | Out-Null }
|
||||
|
||||
$artifacts = @($Zip, $CliZip)
|
||||
$artifacts = @($Zip, $CliZip, $RelayZip)
|
||||
if (Test-Path $Msi) { $artifacts += $Msi }
|
||||
Get-Item $artifacts | Format-Table Name, Length
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
# Build the landing page into a folder ready for GitHub Pages:
|
||||
# sh site/build.sh <out-dir> [<previous-build-dir>]
|
||||
# Needs curl, jq and ffmpeg. GH_TOKEN, when set, authenticates the GitHub API call.
|
||||
# RELEASE_JSON=<file> uses that file instead of asking the API. The demo video is re-encoded only
|
||||
# when docs/demo.mp4 changed since <previous-build-dir>, so a release-only update stays cheap.
|
||||
# <out-dir> must be new or empty.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
OUT="${1:?usage: sh site/build.sh <out-dir> [<previous-build-dir>]}"
|
||||
PREV="${2:-}"
|
||||
REPO="${GITHUB_REPOSITORY:-nxtime/papercraft}"
|
||||
|
||||
if [ -n "$(ls -A "$OUT" 2>/dev/null)" ]; then
|
||||
echo "$OUT is not empty; pass a new folder." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$OUT/media/agents"
|
||||
|
||||
cp site/index.html site/site.css site/site.js "$OUT/"
|
||||
cp assets/app-icon/papercraft.svg "$OUT/media/icon.svg"
|
||||
for agent in claude openai cursor goose kimi mistral qwen cline githubcopilot nousresearch; do
|
||||
cp "assets/agents/$agent.svg" "$OUT/media/agents/"
|
||||
done
|
||||
for shot in hero chat annotations variables; do
|
||||
cp "docs/screenshots/readme/$shot.png" "$OUT/media/"
|
||||
done
|
||||
# GitHub Pages runs Jekyll otherwise, which is slower and has nothing to do here.
|
||||
touch "$OUT/.nojekyll"
|
||||
|
||||
# The page reads this shape; site.js maps the API's own shape onto it the same way.
|
||||
if [ -n "${RELEASE_JSON:-}" ]; then
|
||||
cp "$RELEASE_JSON" "$OUT/release.json"
|
||||
else
|
||||
set -- -fsSL -H "Accept: application/vnd.github+json"
|
||||
if [ -n "${GH_TOKEN:-}" ]; then
|
||||
set -- "$@" -H "Authorization: Bearer $GH_TOKEN"
|
||||
fi
|
||||
curl "$@" "https://api.github.com/repos/$REPO/releases/latest" | jq '{
|
||||
tag: .tag_name,
|
||||
name: .name,
|
||||
publishedAt: .published_at,
|
||||
url: .html_url,
|
||||
assets: [.assets[] | {name, size, url: .browser_download_url, digest}]
|
||||
}' > "$OUT/release.json"
|
||||
fi
|
||||
jq -e '.tag and (.assets | length > 0)' "$OUT/release.json" > /dev/null
|
||||
|
||||
SRC_SUM="$(sha256sum docs/demo.mp4 | cut -d' ' -f1)"
|
||||
if [ -n "$PREV" ] && [ -f "$PREV/media/demo.mp4" ] && [ "$(cat "$PREV/media/demo.source" 2>/dev/null)" = "$SRC_SUM" ]; then
|
||||
cp "$PREV/media/demo.mp4" "$PREV/media/poster.jpg" "$OUT/media/"
|
||||
else
|
||||
# 720p30 without audio keeps the tour near 2 MB, against 26 MB for the 1080p60 source.
|
||||
ffmpeg -loglevel error -y -i docs/demo.mp4 -vf "scale=1280:-2,fps=30" -c:v libx264 -crf 28 \
|
||||
-preset slow -pix_fmt yuv420p -an -movflags +faststart "$OUT/media/demo.mp4"
|
||||
ffmpeg -loglevel error -y -ss 1.5 -i docs/demo.mp4 -frames:v 1 -vf scale=1280:-2 -q:v 4 \
|
||||
"$OUT/media/poster.jpg"
|
||||
fi
|
||||
echo "$SRC_SUM" > "$OUT/media/demo.source"
|
||||
|
||||
echo "Built $(jq -r .tag "$OUT/release.json") into $OUT"
|
||||
@@ -0,0 +1,375 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<title>PaperCraft</title>
|
||||
<meta name="claude:viewport-fit-sides" content="cover">
|
||||
<meta name="description" content="A native, local-first design and whiteboard canvas that AI agents can drive over MCP and a CLI.">
|
||||
<meta name="theme-color" content="#11111b">
|
||||
<meta property="og:title" content="PaperCraft">
|
||||
<meta property="og:description" content="A native canvas that AI agents can drive. Local-first, no account, open source.">
|
||||
<meta property="og:image" content="media/poster.jpg">
|
||||
<link rel="icon" href="media/icon.svg" type="image/svg+xml">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap">
|
||||
<link rel="stylesheet" href="site.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="stage" aria-hidden="true"><div class="glow a"></div><div class="glow b"></div><div class="glow c"></div><div class="spot"></div></div>
|
||||
|
||||
<header class="nav" id="nav">
|
||||
<div class="wrap">
|
||||
<div class="nav-bar acrylic">
|
||||
<a class="brand" href="#top"><img src="media/icon.svg" alt="">PaperCraft</a>
|
||||
<nav class="nav-links" aria-label="Sections">
|
||||
<a href="#live">Agents</a>
|
||||
<a href="#features">Features</a>
|
||||
<a href="#local">Local-first</a>
|
||||
<a href="#download">Download</a>
|
||||
<a href="https://github.com/nxtime/papercraft">GitHub</a>
|
||||
</nav>
|
||||
<a class="btn btn-primary btn-sm" href="#download">Download <span class="ver" data-version>latest</span></a>
|
||||
<span class="progress" aria-hidden="true"></span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
<section class="hero wrap">
|
||||
<img class="hero-mark rise" src="media/icon.svg" alt="PaperCraft app icon" width="76" height="76">
|
||||
<div class="eyebrow rise d1">Rust · egui · wgpu · MCP</div>
|
||||
<h1 class="serif" id="headline">A native canvas that <em>AI agents</em> can drive.</h1>
|
||||
<p class="lede rise d2">PaperCraft is an infinite-canvas design tool and whiteboard. It runs on your desktop with no account and no cloud, and it serves the Model Context Protocol so Claude Code, Codex and other agents can edit the open canvas, screenshot it and lint it, one undo step per call.</p>
|
||||
<div class="cta rise d3">
|
||||
<a class="btn btn-primary" id="hero-dl" href="#download">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12m0 0-5-5m5 5 5-5M4 21h16"/></svg>
|
||||
<span id="hero-dl-label">Download</span> <span class="ver" data-version>latest</span>
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/nxtime/papercraft">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 .5a11.5 11.5 0 0 0-3.64 22.41c.58.1.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.52-1.33-1.28-1.69-1.28-1.69-1.04-.71.08-.7.08-.7 1.15.08 1.76 1.19 1.76 1.19 1.03 1.76 2.69 1.25 3.35.96.1-.75.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.17 1.18a11 11 0 0 1 5.77 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.11 3.05.74.8 1.19 1.83 1.19 3.09 0 4.42-2.7 5.39-5.26 5.68.41.36.78 1.06.78 2.14v3.17c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .5Z"/></svg>
|
||||
Source on GitHub
|
||||
</a>
|
||||
</div>
|
||||
<div class="facts rise d3"><span>Local-first, no account</span><span>98 MCP tools</span><span>Windows · macOS · Linux</span><span>MIT or Apache-2.0</span></div>
|
||||
</section>
|
||||
|
||||
<section class="showcase wrap" aria-label="Product tour">
|
||||
<div class="window acrylic" id="window">
|
||||
<div class="chrome" aria-hidden="true"><i></i><i></i><i></i><span>PaperCraft · Landing page</span></div>
|
||||
<video src="media/demo.mp4" poster="media/poster.jpg" autoplay muted loop playsinline preload="metadata" aria-label="A 40-second tour of the editor, chat mode, annotations and design systems"></video>
|
||||
</div>
|
||||
<p class="caption">A 40-second tour, rendered from the app's own design rather than screen-recorded.</p>
|
||||
</section>
|
||||
|
||||
<section class="marquee" aria-label="Agents that work with PaperCraft">
|
||||
<p class="eyebrow wrap">Chat mode runs the agents you already use</p>
|
||||
<div class="track">
|
||||
<div class="belt">
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/claude.svg)"></i>Claude Code</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/openai.svg)"></i>Codex</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/cursor.svg)"></i>Cursor</span>
|
||||
<span class="chip acrylic"><i class="none"></i>OpenCode</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/goose.svg)"></i>Goose</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/kimi.svg)"></i>Kimi</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/mistral.svg)"></i>Mistral Vibe</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/qwen.svg)"></i>Qwen Code</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/cline.svg)"></i>Cline</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/githubcopilot.svg)"></i>Copilot CLI</span>
|
||||
<span class="chip acrylic"><i style="--logo:url(media/agents/nousresearch.svg)"></i>Hermes</span>
|
||||
<span class="chip acrylic"><i class="none"></i>Any ACP agent</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" id="live">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">For agents</div>
|
||||
<h2 class="serif">Watch an agent <em>build a screen.</em></h2>
|
||||
<p>Each tool call is one undo step. The agent writes HTML that becomes real layers, switches the theme by changing a variable mode, takes a screenshot and lints the result.</p>
|
||||
</div>
|
||||
<div class="live acrylic reveal" id="live-demo">
|
||||
<div class="log">
|
||||
<div class="log-head"><span class="dot" aria-hidden="true"></span>Agent run · Sign in<span class="undo" id="undo-count">0 undo steps</span></div>
|
||||
<ol class="calls" id="calls">
|
||||
<li class="call"><span class="st"></span><b>create_artboard</b><span class="ms">9 ms</span><span class="arg">{"name": "Sign in", "width": 360, "height": 480}</span></li>
|
||||
<li class="call"><span class="st"></span><b>set_variables</b><span class="ms">4 ms</span><span class="arg">{"bg", "surface", "text", "accent"} with Dark and Light modes</span></li>
|
||||
<li class="call"><span class="st"></span><b>write_html</b><span class="ms">18 ms</span><span class="arg"><div style="display:flex; flex-direction:column; gap:12px"> → 6 layers</span></li>
|
||||
<li class="call"><span class="st"></span><b>update_styles</b><span class="ms">3 ms</span><span class="arg">{"#submit": {"background": "var(--accent)"}}</span></li>
|
||||
<li class="call"><span class="st"></span><b>set_variable_mode</b><span class="ms">2 ms</span><span class="arg">{"collection": "Colors", "mode": "Light"}</span></li>
|
||||
<li class="call"><span class="st"></span><b>get_screenshot</b><span class="ms">41 ms</span><span class="arg">sign-in@2x.png</span></li>
|
||||
<li class="call"><span class="st"></span><b>lint_design</b><span class="ms">7 ms</span><span class="arg">contrast, 4px grid, type scale, touch targets</span></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="board-area" aria-hidden="true">
|
||||
<span class="board-label">Sign in</span>
|
||||
<span class="mode-pill" id="mode-pill">Mode: Dark</span>
|
||||
<div class="board" id="board">
|
||||
<div class="a-logo"></div>
|
||||
<div class="a-title">Welcome back</div>
|
||||
<div class="a-sub">Sign in to continue to your workspace.</div>
|
||||
<div class="a-field"></div>
|
||||
<div class="a-field"></div>
|
||||
<div class="a-btn">Sign in</div>
|
||||
</div>
|
||||
<span class="lint" id="lint">✓ lint_design · 0 issues</span>
|
||||
<span class="flash" id="flash"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" id="features">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">What you get</div>
|
||||
<h2 class="serif">A real design tool, <em>not a chat window</em> with a canvas bolted on.</h2>
|
||||
<p>Frames, auto layout, components, variables and a hand-drawn whiteboard on one GPU-rendered canvas.</p>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
<article class="feature reveal">
|
||||
<div class="copy">
|
||||
<div class="eyebrow">The editor</div>
|
||||
<h3 class="serif">Layers, canvas, inspector. <em>One native window.</em></h3>
|
||||
<p>Built in Rust on egui and wgpu. Cached frames take about 3 ms with ten thousand visible layers.</p>
|
||||
<ul class="ticks">
|
||||
<li>Auto layout with gap, padding, hug, fill, wrap and constraints</li>
|
||||
<li>Pen and Bézier editing, boolean operations, gradients and blend modes</li>
|
||||
<li>A command palette over every action, page and layer</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tilt"><div class="shot acrylic"><img src="media/hero.png" alt="PaperCraft with a habit-tracker design open: layers on the left, three annotated screens on the canvas, the inspector on the right" loading="lazy"></div></div>
|
||||
</article>
|
||||
|
||||
<article class="feature reveal">
|
||||
<div class="copy">
|
||||
<div class="eyebrow">Chat mode</div>
|
||||
<h3 class="serif">Pick an agent. <em>Describe the screen.</em></h3>
|
||||
<p>A conversation sits next to the canvas. Point at layers with @ mentions or a picked area, and the agent gets a crop with your notes drawn on it.</p>
|
||||
<ul class="ticks">
|
||||
<li>Claude Code and Codex through their CLIs, with your own logins</li>
|
||||
<li>Any ACP agent on your PATH, or the Anthropic API with your key</li>
|
||||
<li>Every run can be reviewed, reverted one change at a time, or accepted</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tilt"><div class="shot acrylic"><img src="media/chat.png" alt="Chat mode: a conversation with an agent beside the canvas" loading="lazy"></div></div>
|
||||
</article>
|
||||
|
||||
<article class="feature reveal">
|
||||
<div class="copy">
|
||||
<div class="eyebrow">Annotations</div>
|
||||
<h3 class="serif">Annotate <em>in place.</em> Agents read the notes.</h3>
|
||||
<p>Pin typed notes for rules, behaviour, content and data to any layer, with a template per project. They travel with the design into the handoff spec.</p>
|
||||
</div>
|
||||
<div class="tilt"><div class="shot acrylic"><img src="media/annotations.png" alt="The Annotations view listing notes grouped by screen, with their category chips" loading="lazy"></div></div>
|
||||
</article>
|
||||
|
||||
<article class="feature reveal">
|
||||
<div class="copy">
|
||||
<div class="eyebrow">Design systems</div>
|
||||
<h3 class="serif">Tokens out as CSS, DTCG or Tailwind. <em>Code out as JSX.</em></h3>
|
||||
<p>Variables with modes and aliases, components that work across projects, and shared libraries you publish, enable and update.</p>
|
||||
<ul class="ticks">
|
||||
<li>Light and dark are modes on one artboard, never copies</li>
|
||||
<li>Lint for contrast, the 4px grid, type scale and touch targets, with auto-fix</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tilt"><div class="shot acrylic"><img src="media/variables.png" alt="The Variables modal with Light and Dark values for brand, foreground and background" loading="lazy"></div></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" aria-labelledby="more-title">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">And the rest</div>
|
||||
<h2 class="serif" id="more-title">Sketch it, <em>present it,</em> ship it.</h2>
|
||||
</div>
|
||||
<div class="bento stagger">
|
||||
<article class="tile wide acrylic lit reveal">
|
||||
<div class="art">
|
||||
<svg class="sketch" viewBox="0 0 360 92" width="100%" height="92" preserveAspectRatio="xMinYMid meet" aria-hidden="true">
|
||||
<path d="M12 18 C60 12 110 16 140 19 C143 40 141 60 138 76 C96 79 50 78 14 74 C11 55 13 36 12 18Z"/>
|
||||
<path d="M146 46 C180 44 210 46 236 47 M226 38 L238 47 L226 56"/>
|
||||
<rect class="note" x="252" y="14" width="78" height="64" rx="4" transform="rotate(3 291 46)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3>Whiteboard mode</h3>
|
||||
<p>Hand-drawn shapes, arrows bound to shapes, elbow connectors, sticky notes, freehand ink and a laser pointer, on the same canvas as your screens.</p>
|
||||
</article>
|
||||
<article class="tile acrylic lit reveal">
|
||||
<div class="art"><span class="fmt in">.fig</span><span class="fmt in">.excalidraw</span><span class="arrow-dash"></span><span class="fmt out">PDF</span></div>
|
||||
<h3>Import and export</h3>
|
||||
<p>Figma, Excalidraw and SVG in. PNG at 1 to 4x, SVG and vector PDF out, for a selection, a frame or a page.</p>
|
||||
</article>
|
||||
<article class="tile acrylic lit reveal">
|
||||
<div class="art"><div class="versions"><div>Agent run · Sign in</div><div>Auto-version · 14:02</div><div>Before handoff</div></div></div>
|
||||
<h3>Versions and runs</h3>
|
||||
<p>Named and automatic versions per page. An agent run saves one before its first edit, so you can always go back.</p>
|
||||
</article>
|
||||
<article class="tile acrylic lit reveal">
|
||||
<div class="art"><span class="fmt">Welcome</span><span class="arrow-dash"></span><span class="fmt">Today</span></div>
|
||||
<h3>Present mode</h3>
|
||||
<p>Prototype links between frames, followed with a camera glide.</p>
|
||||
</article>
|
||||
<article class="tile acrylic lit reveal">
|
||||
<div class="art"><div class="peers"><span style="background:var(--mauve)">M</span><span style="background:var(--blue)">A</span><span style="background:var(--green)">J</span></div></div>
|
||||
<h3>Shared projects</h3>
|
||||
<p>An optional relay merges concurrent edits to a page. Without one, nothing leaves your machine.</p>
|
||||
</article>
|
||||
<article class="tile wide acrylic lit reveal">
|
||||
<div class="art">
|
||||
<div class="swatches" aria-hidden="true">
|
||||
<span style="background:#eff1f5"></span><span style="background:#303446"></span><span style="background:#24273a"></span><span style="background:#1e1e2e"></span><span style="background:#11111b"></span>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Themes</h3>
|
||||
<p>System, Light and Dark, plus the four Catppuccin flavours: Latte, Frappé, Macchiato and Mocha.</p>
|
||||
</article>
|
||||
<article class="tile acrylic lit reveal">
|
||||
<div class="art"><span class="fmt">wasm32</span><span class="arrow-dash"></span><span class="fmt out">browser</span></div>
|
||||
<h3>Web build</h3>
|
||||
<p>The same editor compiled to WebAssembly, served by <code>papercraft-relay</code>.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" id="local">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">Local-first</div>
|
||||
<h2 class="serif">Your project is <em>a folder on your disk.</em></h2>
|
||||
<p>Plain JSON you can back up, diff and commit. No account to make and no server to trust.</p>
|
||||
</div>
|
||||
<div class="local">
|
||||
<div class="tree acrylic reveal">
|
||||
<pre><span class="path">~/Documents/papercraft/</span><span class="d">Pulse/</span>
|
||||
├── project.json <span class="c"># manifest, variables, components</span>
|
||||
├── <span class="d">pages/</span> <span class="c"># one JSON file per page</span>
|
||||
├── <span class="d">versions/</span> <span class="c"># named and automatic versions</span>
|
||||
├── <span class="d">runs/</span> <span class="c"># agent runs, for review and revert</span>
|
||||
└── <span class="d">chats/</span> <span class="c"># chat threads with their tool calls</span></pre>
|
||||
</div>
|
||||
<div class="points stagger">
|
||||
<div class="point acrylic lit reveal"><h3>Atomic autosave</h3><p>Every write is atomic, and older files are migrated forward when they load.</p></div>
|
||||
<div class="point acrylic lit reveal"><h3>Works with the app closed</h3><p><code>papercraft-cli --offline</code> runs the same tools on the project store, screenshots included.</p></div>
|
||||
<div class="point acrylic lit reveal"><h3>Keys stay in your OS</h3><p>API keys live in the system credential store and never in a project file, chat or log.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" id="agents">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">Connect</div>
|
||||
<h2 class="serif">Same tools, <em>three ways in.</em></h2>
|
||||
<p>The MCP server is built into the app and listens on loopback only. Browser requests and foreign hosts are rejected.</p>
|
||||
</div>
|
||||
<div class="agents stagger">
|
||||
<div class="card acrylic lit reveal">
|
||||
<div class="stat" id="tool-count" data-to="98">98</div>
|
||||
<h3>MCP tools, one undo step each</h3>
|
||||
<div class="ways">
|
||||
<div class="way"><b>MCP over HTTP</b><span>Built into the app at <code>127.0.0.1:29980/mcp</code>.</span></div>
|
||||
<div class="way"><b>MCP over stdio</b><span><code>papercraft-mcp</code> relays to the running app.</span></div>
|
||||
<div class="way"><b>Shell</b><span><code>papercraft-cli</code> calls the same tools, so the agent needs no schemas in context.</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card acrylic lit reveal">
|
||||
<h3>Register it once</h3>
|
||||
<p>Start PaperCraft, then add it to your agent.</p>
|
||||
<div class="code">
|
||||
<button class="copy-btn" type="button" data-copy="claude mcp add --transport http --scope user papercraft http://127.0.0.1:29980/mcp">Copy</button>
|
||||
<pre><span class="c"># Claude Code</span>
|
||||
<span class="k">claude</span> mcp add --transport http --scope user \
|
||||
papercraft http://127.0.0.1:29980/mcp</pre>
|
||||
</div>
|
||||
<div class="code">
|
||||
<button class="copy-btn" type="button" data-copy="codex mcp add papercraft --url http://127.0.0.1:29980/mcp">Copy</button>
|
||||
<pre><span class="c"># Codex</span>
|
||||
<span class="k">codex</span> mcp add papercraft --url http://127.0.0.1:29980/mcp</pre>
|
||||
</div>
|
||||
<div class="code">
|
||||
<button class="copy-btn" type="button" data-copy="papercraft-cli call get_screenshot '{"nodeId":"abc123:1"}' --project Demo -o card.png">Copy</button>
|
||||
<pre><span class="c"># No app running</span>
|
||||
<span class="k">papercraft-cli</span> call get_screenshot '{"nodeId":"abc123:1"}' \
|
||||
--project Demo -o card.png</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" aria-labelledby="keys-title">
|
||||
<div class="section-head reveal">
|
||||
<div class="eyebrow">Keyboard first</div>
|
||||
<h2 class="serif" id="keys-title">The shortcuts <em>you'll use daily.</em></h2>
|
||||
</div>
|
||||
<div class="keys stagger">
|
||||
<div class="key acrylic lit reveal"><kbd data-keys="mod K"></kbd><span>Command palette</span></div>
|
||||
<div class="key acrylic lit reveal"><kbd data-keys="mod J"></kbd><span>Design or Chat mode</span></div>
|
||||
<div class="key acrylic lit reveal"><kbd data-keys="mod alt shift V"></kbd><span>Variables</span></div>
|
||||
<div class="key acrylic lit reveal"><kbd data-keys="N"></kbd><span>Annotate</span></div>
|
||||
<div class="key acrylic lit reveal"><kbd data-keys="/"></kbd><span>Hide the side panels</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="block wrap" id="download">
|
||||
<div class="dl-head reveal">
|
||||
<div class="section-head">
|
||||
<div class="eyebrow">Download</div>
|
||||
<h2 class="serif">Get the <em>latest build.</em></h2>
|
||||
</div>
|
||||
<a class="release-pill acrylic" id="release-link" href="https://github.com/nxtime/papercraft/releases/latest">
|
||||
<span class="pulse" aria-hidden="true"></span>
|
||||
<span><b data-version>latest</b> · <span id="release-date">GitHub releases</span></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="platforms stagger" id="platforms">
|
||||
<article class="platform acrylic">
|
||||
<h3>Loading the latest release</h3>
|
||||
<p class="note">All builds are also on the <a href="https://github.com/nxtime/papercraft/releases/latest">GitHub releases page</a>.</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="dl-foot">
|
||||
<div class="card acrylic reveal">
|
||||
<h3>First run</h3>
|
||||
<p>The Windows zip is unsigned, so SmartScreen may ask you to confirm. macOS builds are ad-hoc signed and not notarized: open the app with right-click, Open the first time.</p>
|
||||
</div>
|
||||
<div class="card acrylic reveal">
|
||||
<h3>Build from source</h3>
|
||||
<p>Rust 1.95 or newer, then <code>cargo build --release</code>. Linux needs Vulkan or GL and the usual X11 or Wayland libraries.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wrap">
|
||||
<div class="status acrylic reveal">
|
||||
<div>
|
||||
<div class="eyebrow">Status</div>
|
||||
<h2 class="serif">Alpha, <em>and used daily.</em></h2>
|
||||
<p>PaperCraft is good for screens, design systems and sketches, and it is not yet a replacement for a mature design tool. The roadmap lists what is missing, in priority order.</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn btn-ghost" href="https://github.com/nxtime/papercraft/blob/main/ROADMAP.md">Roadmap</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/nxtime/papercraft#status-and-known-limits">Known limits</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="wrap foot">
|
||||
<span>PaperCraft is open source, licensed MIT or Apache-2.0.</span>
|
||||
<nav aria-label="Links">
|
||||
<a href="https://github.com/nxtime/papercraft">GitHub</a>
|
||||
<a href="https://github.com/nxtime/papercraft/releases">Releases</a>
|
||||
<a href="https://github.com/nxtime/papercraft/blob/main/docs/agents.md">Agent guide</a>
|
||||
<a href="https://github.com/nxtime/papercraft/blob/main/CONTRIBUTING.md">Contributing</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="toast acrylic" id="toast" role="status" aria-live="polite"></div>
|
||||
|
||||
<script src="site.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,484 @@
|
||||
/* The landing page sits on the app's own dark canvas: acrylic panels over a dot grid lit by
|
||||
lavender glows, with the palette of the app icon (Catppuccin Mocha). */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--crust: #11111b;
|
||||
--base: #181825;
|
||||
--mantle: #1e1e2e;
|
||||
--surface: #313244;
|
||||
--overlay: #45475a;
|
||||
--text: #cdd6f4;
|
||||
--subtext: #a6adc8;
|
||||
--muted: #7f849c;
|
||||
--mauve: #cba6f7;
|
||||
--mauve-hi: #d9bcfa;
|
||||
--mauve-deep: #b48ef0;
|
||||
--on-mauve: #1e1e2e;
|
||||
--green: #a6e3a1;
|
||||
--blue: #89b4fa;
|
||||
--peach: #fab387;
|
||||
--glass: rgb(30 30 46 / 0.55);
|
||||
--glass-strong: rgb(30 30 46 / 0.8);
|
||||
--glass-edge: rgb(205 214 244 / 0.09);
|
||||
--glass-hi: rgb(255 255 255 / 0.06);
|
||||
--glow: rgb(203 166 247 / 0.32);
|
||||
--dot: rgb(205 214 244 / 0.07);
|
||||
--well: rgb(17 17 27 / 0.6);
|
||||
|
||||
--display: "Instrument Serif", "Iowan Old Style", Georgia, serif;
|
||||
--sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
--mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
||||
|
||||
--r-panel: 22px;
|
||||
--r-control: 12px;
|
||||
--wrap: 1180px;
|
||||
--ease: cubic-bezier(.2, .7, .2, 1);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--crust);
|
||||
color: var(--text);
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
body::before {
|
||||
content: ""; position: fixed; inset: 0 0 auto; height: env(safe-area-inset-top, 0px);
|
||||
z-index: 30; background: var(--crust);
|
||||
}
|
||||
img { max-width: 100%; }
|
||||
code { font-family: var(--mono); font-size: .92em; }
|
||||
a { color: inherit; }
|
||||
:focus-visible { outline: 2px solid var(--mauve); outline-offset: 3px; border-radius: 6px; }
|
||||
|
||||
/* The canvas behind everything: dot grid, drifting glows and a spotlight that follows the pointer */
|
||||
.stage { position: fixed; inset: 0; z-index: -1; pointer-events: none; background: var(--crust); overflow: hidden; }
|
||||
.stage::before {
|
||||
content: ""; position: absolute; inset: 0;
|
||||
background: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0) 0 0 / 22px 22px;
|
||||
mask-image: radial-gradient(70rem 50rem at 50% 0%, #000 30%, transparent 85%);
|
||||
}
|
||||
.glow { position: absolute; border-radius: 50%; filter: blur(80px); opacity: .55; }
|
||||
.glow.a { width: 52rem; height: 34rem; left: 50%; top: -18rem; margin-left: -26rem; background: rgb(203 166 247 / .32); }
|
||||
.glow.b { width: 34rem; height: 26rem; right: -10rem; top: 45%; background: rgb(137 180 250 / .12); }
|
||||
.glow.c { width: 30rem; height: 24rem; left: -10rem; bottom: -6rem; background: rgb(203 166 247 / .14); }
|
||||
.spot {
|
||||
position: absolute; inset: 0;
|
||||
background: radial-gradient(28rem 28rem at var(--mx, 50%) var(--my, -20%), rgb(203 166 247 / .07), transparent 70%);
|
||||
}
|
||||
|
||||
.wrap {
|
||||
width: 100%; max-width: var(--wrap); margin-inline: auto;
|
||||
padding-left: max(20px, env(safe-area-inset-left, 0px));
|
||||
padding-right: max(20px, env(safe-area-inset-right, 0px));
|
||||
}
|
||||
|
||||
/* Acrylic: translucent fill, backdrop blur, a hairline edge and a top highlight */
|
||||
.acrylic {
|
||||
position: relative;
|
||||
background: linear-gradient(180deg, var(--glass-hi), transparent 40%), var(--glass);
|
||||
backdrop-filter: blur(22px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(160%);
|
||||
border: 1px solid var(--glass-edge);
|
||||
box-shadow: 0 1px 0 var(--glass-hi) inset, 0 24px 60px -24px rgb(0 0 0 / .6);
|
||||
}
|
||||
|
||||
/* Cards light up under the pointer, like a hovered panel in the app */
|
||||
.lit::after {
|
||||
content: ""; position: absolute; inset: -1px; border-radius: inherit; pointer-events: none;
|
||||
background: radial-gradient(22rem 22rem at var(--lx, 50%) var(--ly, 50%), rgb(203 166 247 / .14), transparent 60%);
|
||||
opacity: 0; transition: opacity .4s;
|
||||
}
|
||||
.lit:hover::after { opacity: 1; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono); font-size: 12px; letter-spacing: .18em;
|
||||
text-transform: uppercase; color: var(--muted);
|
||||
}
|
||||
.serif { font-family: var(--display); font-weight: 400; letter-spacing: -.01em; }
|
||||
em { font-style: italic; color: var(--mauve); }
|
||||
|
||||
/* Nav */
|
||||
header.nav { position: sticky; top: env(safe-area-inset-top, 0px); z-index: 20; padding-block: 14px; }
|
||||
.nav-bar {
|
||||
display: flex; align-items: center; gap: 16px; padding: 8px 8px 8px 14px; border-radius: 999px;
|
||||
transition: background .3s, box-shadow .3s;
|
||||
}
|
||||
.scrolled .nav-bar { background: var(--glass-strong); }
|
||||
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; font-weight: 600; }
|
||||
.brand img { width: 28px; height: 28px; }
|
||||
.nav-links { display: flex; gap: 4px; margin-left: auto; }
|
||||
.nav-links a {
|
||||
text-decoration: none; color: var(--subtext); font-size: 14px;
|
||||
padding: 6px 12px; border-radius: 999px; transition: color .2s, background .2s;
|
||||
}
|
||||
.nav-links a:hover, .nav-links a.active { color: var(--text); background: var(--glass-hi); }
|
||||
.progress {
|
||||
position: absolute; left: 24px; right: 24px; bottom: -1px; height: 1px; border-radius: 1px;
|
||||
background: linear-gradient(90deg, transparent, var(--mauve), transparent);
|
||||
transform-origin: 0 50%; transform: scaleX(var(--p, 0)); opacity: .7;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
position: relative; overflow: hidden;
|
||||
display: inline-flex; align-items: center; gap: 10px; text-decoration: none;
|
||||
font: 500 15px/1 var(--sans); padding: 13px 20px; border-radius: 999px;
|
||||
border: 1px solid transparent; cursor: pointer; white-space: nowrap;
|
||||
transition: transform .25s var(--ease), box-shadow .25s, background .25s;
|
||||
}
|
||||
.btn-primary {
|
||||
background: linear-gradient(180deg, var(--mauve-hi), var(--mauve-deep)); color: var(--on-mauve);
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / .25) inset, 0 10px 30px -8px var(--glow);
|
||||
}
|
||||
.btn-primary::before {
|
||||
content: ""; position: absolute; inset: 0; transform: translateX(-120%);
|
||||
background: linear-gradient(100deg, transparent 30%, rgb(255 255 255 / .45) 50%, transparent 70%);
|
||||
}
|
||||
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 0 0 1px rgb(255 255 255 / .3) inset, 0 16px 40px -8px var(--glow); }
|
||||
.btn-primary:hover::before { transition: transform .8s var(--ease); transform: translateX(120%); }
|
||||
.btn-ghost { background: var(--glass); border-color: var(--glass-edge); color: var(--text); backdrop-filter: blur(12px); }
|
||||
.btn-ghost:hover { background: var(--glass-strong); transform: translateY(-2px); }
|
||||
.btn-sm { padding: 9px 14px; font-size: 14px; }
|
||||
.btn svg { width: 16px; height: 16px; flex: none; }
|
||||
.ver { font-family: var(--mono); font-size: 12px; opacity: .7; }
|
||||
|
||||
/* Hero */
|
||||
.hero { text-align: center; padding-block: clamp(48px, 9vw, 104px) 36px; }
|
||||
.hero-mark { width: 76px; height: 76px; margin: 0 auto 22px; display: block; filter: drop-shadow(0 12px 30px var(--glow)); }
|
||||
.hero h1 { font-size: clamp(44px, 7.4vw, 92px); line-height: 1; margin: 18px auto 0; max-width: 14ch; text-wrap: balance; }
|
||||
.hero h1 .w { display: inline-block; }
|
||||
.lede { max-width: 58ch; margin: 22px auto 0; color: var(--subtext); font-size: clamp(16px, 1.6vw, 18px); text-wrap: pretty; }
|
||||
.cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 32px; }
|
||||
.facts {
|
||||
display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 22px; margin-top: 26px;
|
||||
font-family: var(--mono); font-size: 12px; color: var(--muted); letter-spacing: .04em;
|
||||
}
|
||||
.facts span::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--green); margin-right: 8px; vertical-align: 2px; }
|
||||
|
||||
/* Video window: tilted back until it scrolls into view */
|
||||
.showcase { padding-block: 24px 24px; perspective: 1600px; }
|
||||
.window {
|
||||
border-radius: var(--r-panel); padding: 10px;
|
||||
transform: rotateX(var(--tilt, 10deg)) scale(var(--zoom, .96)); transform-origin: 50% 0;
|
||||
box-shadow: 0 0 0 1px var(--glass-edge), 0 40px 120px -30px var(--glow), 0 30px 80px -30px rgb(0 0 0 / .8);
|
||||
}
|
||||
.chrome { display: flex; align-items: center; gap: 8px; padding: 4px 6px 10px; }
|
||||
.chrome i { width: 11px; height: 11px; border-radius: 50%; background: var(--surface); }
|
||||
.chrome span { margin-inline: auto; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.window video { display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 14px; background: var(--base); }
|
||||
.caption { text-align: center; color: var(--muted); font-size: 14px; margin: 18px 0 0; }
|
||||
|
||||
/* Agents marquee */
|
||||
.marquee { padding-block: 40px 8px; }
|
||||
.marquee p { text-align: center; margin: 0 0 18px; }
|
||||
.track {
|
||||
display: flex; overflow: hidden;
|
||||
mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
|
||||
}
|
||||
.belt { display: flex; gap: 12px; padding-right: 12px; flex: none; animation: belt 48s linear infinite; }
|
||||
.track:hover .belt { animation-play-state: paused; }
|
||||
.chip {
|
||||
display: inline-flex; align-items: center; gap: 9px; padding: 9px 14px; border-radius: 999px;
|
||||
font-size: 14px; color: var(--subtext); white-space: nowrap;
|
||||
}
|
||||
.chip i {
|
||||
width: 16px; height: 16px; flex: none; background: currentColor;
|
||||
-webkit-mask: var(--logo) center / contain no-repeat; mask: var(--logo) center / contain no-repeat;
|
||||
}
|
||||
.chip i.none { -webkit-mask: none; mask: none; border-radius: 4px; background: var(--overlay); }
|
||||
@keyframes belt { to { transform: translateX(-100%); } }
|
||||
|
||||
/* Sections */
|
||||
section.block { padding-block: clamp(56px, 8vw, 96px); }
|
||||
.section-head { max-width: 720px; margin-bottom: 44px; }
|
||||
.section-head h2 { font-size: clamp(36px, 5vw, 60px); line-height: 1.04; margin: 14px 0 0; text-wrap: balance; }
|
||||
.section-head p { color: var(--subtext); margin: 16px 0 0; max-width: 60ch; }
|
||||
|
||||
/* Live agent demo */
|
||||
.live { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: 0; border-radius: var(--r-panel); overflow: hidden; }
|
||||
.log { padding: 22px; border-right: 1px solid var(--glass-edge); min-width: 0; display: flex; flex-direction: column; }
|
||||
.log-head { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--subtext); margin-bottom: 14px; }
|
||||
.log-head .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2.4s infinite; }
|
||||
.log-head .undo { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||
.calls { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; font: 13px/1.5 var(--mono); }
|
||||
.call {
|
||||
display: grid; grid-template-columns: 14px minmax(0, 1fr) auto; gap: 4px 10px; align-items: start;
|
||||
padding: 9px 11px; border-radius: 10px; background: var(--well); border: 1px solid var(--glass-edge);
|
||||
opacity: .28; transition: opacity .4s, border-color .4s, transform .4s var(--ease);
|
||||
}
|
||||
.call .st { width: 14px; height: 14px; margin-top: 3px; border-radius: 50%; border: 1.5px solid var(--overlay); }
|
||||
.call b { font-weight: 500; color: var(--text); }
|
||||
.call .arg { grid-column: 2 / span 2; color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
|
||||
.call .ms { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.call.run { opacity: 1; border-color: rgb(203 166 247 / .5); transform: translateX(4px); }
|
||||
.call.run .st { border-color: var(--mauve); border-top-color: transparent; animation: spin .7s linear infinite; }
|
||||
.call.done { opacity: 1; }
|
||||
.call.done .st { border: 0; background: var(--green) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8.5l2.5 2.5L12 5.5' fill='none' stroke='%231e1e2e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 12px no-repeat; }
|
||||
.call:not(.done) .ms { visibility: hidden; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
@keyframes pulse { 0% { box-shadow: 0 0 0 0 rgb(166 227 161 / .6); } 70%, 100% { box-shadow: 0 0 0 8px rgb(166 227 161 / 0); } }
|
||||
|
||||
.board-area {
|
||||
position: relative; min-width: 0; display: grid; place-items: center; padding: 40px 24px;
|
||||
background: radial-gradient(circle at 1px 1px, var(--dot) 1px, transparent 0) 0 0 / 18px 18px, rgb(17 17 27 / .35);
|
||||
}
|
||||
.board-label { position: absolute; top: 16px; left: 20px; font: 12px var(--mono); color: var(--muted); }
|
||||
.mode-pill {
|
||||
position: absolute; top: 12px; right: 16px; font: 500 12px var(--mono); padding: 4px 10px; border-radius: 999px;
|
||||
background: var(--well); border: 1px solid var(--glass-edge); color: var(--subtext); transition: color .4s;
|
||||
}
|
||||
.board {
|
||||
--a-bg: #1e1e2e; --a-surface: #313244; --a-text: #cdd6f4; --a-sub: #a6adc8; --a-accent: #cba6f7; --a-on: #1e1e2e;
|
||||
width: min(100%, 300px); aspect-ratio: 3 / 4; border-radius: 18px; padding: 26px 22px;
|
||||
display: flex; flex-direction: column; gap: 12px;
|
||||
outline: 1.5px dashed transparent; outline-offset: 6px;
|
||||
background: transparent; transition: background .6s, outline-color .4s, box-shadow .6s;
|
||||
}
|
||||
.board.light { --a-bg: #eff1f5; --a-surface: #dce0e8; --a-text: #4c4f69; --a-sub: #6c6f85; --a-accent: #8839ef; --a-on: #eff1f5; }
|
||||
.board.s1 { outline-color: rgb(203 166 247 / .6); }
|
||||
.board.s2 { background: var(--a-bg); box-shadow: 0 30px 60px -30px rgb(0 0 0 / .8); }
|
||||
.board > * { opacity: 0; transform: translateY(8px); transition: opacity .45s var(--ease), transform .45s var(--ease), background .6s, color .6s; }
|
||||
.board.s3 > * { opacity: 1; transform: none; }
|
||||
.board.s3 > *:nth-child(2) { transition-delay: .06s; } .board.s3 > *:nth-child(3) { transition-delay: .12s; }
|
||||
.board.s3 > *:nth-child(4) { transition-delay: .18s; } .board.s3 > *:nth-child(5) { transition-delay: .24s; }
|
||||
.board.s3 > *:nth-child(6) { transition-delay: .3s; }
|
||||
.a-logo { width: 34px; height: 34px; border-radius: 10px; background: var(--a-surface); }
|
||||
.a-title { font: 600 22px/1.2 var(--sans); color: var(--a-text); }
|
||||
.a-sub { font-size: 12px; color: var(--a-sub); margin-top: -6px; }
|
||||
.a-field { height: 36px; border-radius: 9px; background: var(--a-surface); }
|
||||
.a-btn {
|
||||
height: 38px; border-radius: 10px; margin-top: auto; display: grid; place-items: center;
|
||||
font: 600 13px var(--sans); background: var(--a-surface); color: var(--a-sub);
|
||||
}
|
||||
.board.s4 .a-btn { background: var(--a-accent); color: var(--a-on); }
|
||||
.board.s4 .a-logo { background: var(--a-accent); }
|
||||
.flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; }
|
||||
.flash.on { animation: flash .5s ease-out; }
|
||||
@keyframes flash { 0% { opacity: .35; } 100% { opacity: 0; } }
|
||||
.lint {
|
||||
position: absolute; bottom: 16px; left: 50%; display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 7px 12px; border-radius: 999px; font: 500 12px var(--mono); color: var(--green);
|
||||
background: var(--glass-strong); border: 1px solid rgb(166 227 161 / .35);
|
||||
opacity: 0; transform: translate(-50%, 10px); transition: opacity .4s, transform .4s var(--ease);
|
||||
}
|
||||
.lint.on { opacity: 1; transform: translate(-50%, 0); }
|
||||
|
||||
/* Feature rows */
|
||||
.features { display: grid; gap: 24px; }
|
||||
.feature {
|
||||
display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(24px, 4vw, 56px);
|
||||
align-items: center; padding-block: 28px;
|
||||
}
|
||||
.feature:nth-child(even) { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
|
||||
.feature:nth-child(even) .copy { order: 2; }
|
||||
.feature h3 { font-size: clamp(30px, 3.6vw, 44px); line-height: 1.06; margin: 12px 0 0; text-wrap: balance; }
|
||||
.feature p { color: var(--subtext); margin: 14px 0 0; }
|
||||
.ticks { margin: 16px 0 0; padding: 0; list-style: none; display: grid; gap: 8px; color: var(--subtext); font-size: 15px; }
|
||||
.ticks li { display: flex; gap: 10px; }
|
||||
.ticks li::before { content: ""; flex: none; width: 6px; height: 6px; margin-top: 10px; border-radius: 2px; background: var(--mauve); }
|
||||
.tilt { perspective: 1200px; }
|
||||
.shot {
|
||||
border-radius: var(--r-panel); padding: 8px;
|
||||
transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)); transition: transform .5s var(--ease);
|
||||
}
|
||||
.shot img { display: block; width: 100%; height: auto; border-radius: 15px; }
|
||||
|
||||
/* Bento grid */
|
||||
.bento { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
||||
.tile { border-radius: var(--r-panel); padding: 24px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.tile.wide { grid-column: span 2; }
|
||||
.tile h3 { margin: 0; font-size: 17px; font-weight: 600; }
|
||||
.tile p { margin: 0; color: var(--subtext); font-size: 15px; }
|
||||
.tile .art { height: 92px; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.fmt {
|
||||
font: 500 12px var(--mono); padding: 6px 10px; border-radius: 8px; background: var(--well);
|
||||
border: 1px solid var(--glass-edge); color: var(--subtext);
|
||||
}
|
||||
.fmt.in { color: var(--blue); } .fmt.out { color: var(--mauve); }
|
||||
.arrow-dash { flex: 1; min-width: 30px; height: 1px; background: repeating-linear-gradient(90deg, var(--overlay) 0 6px, transparent 6px 12px); background-size: 24px 1px; animation: dash 1.2s linear infinite; }
|
||||
@keyframes dash { to { background-position: 24px 0; } }
|
||||
.swatches { display: flex; gap: 8px; }
|
||||
.swatches span {
|
||||
width: 40px; height: 56px; border-radius: 10px; border: 1px solid var(--glass-edge);
|
||||
transition: transform .4s var(--ease);
|
||||
}
|
||||
.tile:hover .swatches span { transform: translateY(-6px); }
|
||||
.tile:hover .swatches span:nth-child(2) { transition-delay: .04s; } .tile:hover .swatches span:nth-child(3) { transition-delay: .08s; }
|
||||
.tile:hover .swatches span:nth-child(4) { transition-delay: .12s; } .tile:hover .swatches span:nth-child(5) { transition-delay: .16s; }
|
||||
.sketch path { fill: none; stroke: var(--mauve); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 340; stroke-dashoffset: 340; }
|
||||
.sketch .note { fill: var(--peach); opacity: .85; }
|
||||
.in-view .sketch path { animation: draw 1.6s var(--ease) forwards; }
|
||||
.in-view .sketch path:nth-of-type(2) { animation-delay: .4s; }
|
||||
@keyframes draw { to { stroke-dashoffset: 0; } }
|
||||
.versions { display: grid; gap: 6px; width: 100%; font: 12px var(--mono); color: var(--muted); }
|
||||
.versions div { display: flex; gap: 10px; align-items: center; }
|
||||
.versions div::before { content: ""; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid var(--overlay); flex: none; }
|
||||
.versions div:first-child { color: var(--text); }
|
||||
.versions div:first-child::before { border-color: var(--mauve); background: var(--mauve); }
|
||||
.peers { display: flex; align-items: center; }
|
||||
.peers span {
|
||||
width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; font: 600 13px var(--sans);
|
||||
color: var(--on-mauve); margin-left: -8px; border: 2px solid var(--mantle);
|
||||
}
|
||||
.peers span:first-child { margin-left: 0; }
|
||||
|
||||
/* Local-first */
|
||||
.local { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: stretch; }
|
||||
.tree { border-radius: var(--r-panel); padding: 22px 24px; font: 13px/2 var(--mono); color: var(--subtext); overflow-x: auto; min-width: 0; }
|
||||
.tree .path { color: var(--muted); }
|
||||
.tree .d { color: var(--mauve); }
|
||||
.tree .c { color: var(--muted); }
|
||||
.tree pre { margin: 0; }
|
||||
.points { display: grid; gap: 14px; align-content: start; }
|
||||
.point { border-radius: 16px; padding: 18px 20px; }
|
||||
.point h3 { margin: 0; font-size: 16px; font-weight: 600; }
|
||||
.point p { margin: 4px 0 0; color: var(--subtext); font-size: 15px; }
|
||||
|
||||
/* Agents */
|
||||
.agents { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; }
|
||||
.card { border-radius: var(--r-panel); padding: 24px; min-width: 0; }
|
||||
.card h3 { margin: 0; font-size: 17px; font-weight: 600; }
|
||||
.card > p { color: var(--subtext); margin: 8px 0 0; font-size: 15px; }
|
||||
.stat { font-family: var(--display); font-size: 72px; line-height: 1; color: var(--mauve); font-variant-numeric: tabular-nums; }
|
||||
.ways { display: grid; gap: 14px; margin-top: 18px; }
|
||||
.way { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 12px; font-size: 14px; }
|
||||
.way b { font-weight: 500; }
|
||||
.way span { color: var(--subtext); }
|
||||
.code {
|
||||
position: relative; margin-top: 14px; border-radius: var(--r-control); background: var(--well);
|
||||
border: 1px solid var(--glass-edge); font: 13px/1.7 var(--mono); color: var(--subtext);
|
||||
}
|
||||
.code pre { margin: 0; padding: 14px 16px; padding-right: 70px; overflow-x: auto; }
|
||||
.code .c { color: var(--muted); }
|
||||
.code .k { color: var(--mauve); }
|
||||
.copy-btn {
|
||||
position: absolute; top: 8px; right: 8px; font: 500 12px var(--sans); color: var(--subtext);
|
||||
background: var(--glass-strong); border: 1px solid var(--glass-edge); border-radius: 8px;
|
||||
padding: 5px 9px; cursor: pointer; transition: color .2s, border-color .2s;
|
||||
}
|
||||
.copy-btn:hover { color: var(--text); border-color: rgb(203 166 247 / .4); }
|
||||
|
||||
/* Shortcuts */
|
||||
.keys { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
|
||||
.key { border-radius: 16px; padding: 18px; display: grid; gap: 12px; justify-items: start; }
|
||||
.key span { font-size: 14px; color: var(--subtext); }
|
||||
kbd {
|
||||
display: inline-flex; gap: 4px; font: 500 14px var(--mono); color: var(--text);
|
||||
}
|
||||
kbd b {
|
||||
min-width: 30px; height: 30px; padding: 0 8px; display: grid; place-items: center; font-weight: 500;
|
||||
border-radius: 8px; background: var(--well); border: 1px solid var(--glass-edge);
|
||||
box-shadow: 0 2px 0 rgb(0 0 0 / .4); transition: transform .15s, box-shadow .15s;
|
||||
}
|
||||
.key:hover kbd b { transform: translateY(2px); box-shadow: 0 0 0 rgb(0 0 0 / .4); }
|
||||
|
||||
/* Download */
|
||||
.dl-head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 16px; margin-bottom: 28px; }
|
||||
.dl-head .section-head { margin: 0; }
|
||||
.release-pill {
|
||||
display: inline-flex; align-items: center; gap: 10px; padding: 8px 14px; border-radius: 999px;
|
||||
font-family: var(--mono); font-size: 13px; color: var(--subtext); text-decoration: none;
|
||||
}
|
||||
.release-pill b { color: var(--text); font-weight: 500; }
|
||||
.pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2.4s infinite; }
|
||||
.platforms { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
|
||||
.platform { display: flex; flex-direction: column; gap: 12px; border-radius: var(--r-panel); padding: 22px; min-width: 0; }
|
||||
.platform.mine { box-shadow: 0 0 0 1px rgb(203 166 247 / .55), 0 0 50px -10px var(--glow); }
|
||||
.platform .tag {
|
||||
position: absolute; top: 18px; right: 18px; font: 500 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
|
||||
color: var(--on-mauve); background: var(--mauve); padding: 3px 8px; border-radius: 999px;
|
||||
}
|
||||
.platform .os { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
|
||||
.platform .os svg { width: 28px; height: 28px; color: var(--text); flex: none; }
|
||||
.platform h3 { margin: 0; font-size: 18px; font-weight: 600; }
|
||||
.platform .arch { font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
||||
.file { display: grid; grid-template-columns: minmax(0, 1fr) auto; border-radius: var(--r-control); background: var(--well); border: 1px solid var(--glass-edge); transition: border-color .2s; }
|
||||
.file:hover { border-color: rgb(203 166 247 / .5); }
|
||||
.file a { display: grid; gap: 2px; padding: 12px 14px; text-decoration: none; min-width: 0; }
|
||||
.file .fname { font: 500 14px var(--sans); }
|
||||
.file .fmeta { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; font-family: var(--mono); }
|
||||
.file .sha {
|
||||
align-self: center; margin-right: 8px; font: 500 11px var(--mono); color: var(--muted); cursor: pointer;
|
||||
background: transparent; border: 1px solid var(--glass-edge); border-radius: 7px; padding: 5px 8px;
|
||||
}
|
||||
.file .sha:hover { color: var(--text); }
|
||||
.platform .note { font-size: 13px; color: var(--muted); margin: auto 0 0; }
|
||||
.dl-foot { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 16px; margin-top: 16px; }
|
||||
.dl-foot .card p { margin: 8px 0 0; color: var(--subtext); font-size: 14px; }
|
||||
|
||||
/* Status */
|
||||
.status { border-radius: var(--r-panel); padding: clamp(24px, 4vw, 40px); display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 24px; align-items: center; }
|
||||
.status h2 { font-size: clamp(30px, 4vw, 44px); line-height: 1.06; margin: 10px 0 0; }
|
||||
.status p { color: var(--subtext); margin: 12px 0 0; max-width: 62ch; }
|
||||
.status .actions { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
|
||||
footer { padding-block: 40px; border-top: 1px solid var(--glass-edge); margin-top: 56px; }
|
||||
.foot { display: flex; flex-wrap: wrap; gap: 16px 28px; align-items: center; justify-content: space-between; color: var(--muted); font-size: 14px; }
|
||||
.foot nav { display: flex; gap: 20px; flex-wrap: wrap; }
|
||||
.foot a { text-decoration: none; }
|
||||
.foot a:hover { color: var(--text); }
|
||||
|
||||
.toast {
|
||||
position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); z-index: 40;
|
||||
padding: 10px 16px; border-radius: 999px; font-size: 14px; transform: translate(-50%, 20px); opacity: 0;
|
||||
transition: opacity .3s, transform .3s var(--ease); pointer-events: none;
|
||||
}
|
||||
.toast.on { opacity: 1; transform: translate(-50%, 0); }
|
||||
|
||||
/* Motion. Everything is readable at rest; motion only moves it into place. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.glow.a { animation: drift-a 22s ease-in-out infinite alternate; }
|
||||
.glow.b { animation: drift-b 28s ease-in-out infinite alternate; }
|
||||
.glow.c { animation: drift-c 26s ease-in-out infinite alternate; }
|
||||
.hero-mark { animation: float 6s ease-in-out infinite; }
|
||||
.rise { animation: rise .9s var(--ease) both; }
|
||||
.hero h1 .w { animation: word .9s var(--ease) both; animation-delay: calc(var(--i) * 60ms + 120ms); }
|
||||
.d1 { animation-delay: .1s; } .d2 { animation-delay: .45s; } .d3 { animation-delay: .6s; }
|
||||
.js .reveal { opacity: .001; transform: translateY(24px); transition: opacity .8s var(--ease), transform .8s var(--ease); }
|
||||
.js .reveal.in-view { opacity: 1; transform: none; }
|
||||
.js .stagger > .reveal:nth-child(2) { transition-delay: .08s; }
|
||||
.js .stagger > .reveal:nth-child(3) { transition-delay: .16s; }
|
||||
.js .stagger > .reveal:nth-child(4) { transition-delay: .24s; }
|
||||
.js .stagger > .reveal:nth-child(5) { transition-delay: .32s; }
|
||||
.js .stagger > .reveal:nth-child(6) { transition-delay: .4s; }
|
||||
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
|
||||
@keyframes word { from { opacity: 0; transform: translateY(.4em); filter: blur(6px); } to { opacity: 1; transform: none; filter: none; } }
|
||||
@keyframes float { 50% { transform: translateY(-6px); } }
|
||||
@keyframes drift-a { to { transform: translate(6rem, 4rem) scale(1.1); } }
|
||||
@keyframes drift-b { to { transform: translate(-8rem, -6rem) scale(1.15); } }
|
||||
@keyframes drift-c { to { transform: translate(10rem, -4rem); } }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
.window { --tilt: 0deg; --zoom: 1; }
|
||||
.belt { animation: none; }
|
||||
.track { overflow-x: auto; }
|
||||
.pulse, .log-head .dot, .arrow-dash { animation: none; }
|
||||
.sketch path { stroke-dashoffset: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.feature, .feature:nth-child(even) { grid-template-columns: minmax(0, 1fr); }
|
||||
.feature:nth-child(even) .copy { order: 0; }
|
||||
.agents, .platforms, .local, .dl-foot, .live { grid-template-columns: minmax(0, 1fr); }
|
||||
.log { border-right: 0; border-bottom: 1px solid var(--glass-edge); }
|
||||
.bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.keys { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.status { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.nav-links { display: none; }
|
||||
.nav-bar .btn { margin-left: auto; }
|
||||
.way { grid-template-columns: minmax(0, 1fr); gap: 2px; }
|
||||
.window { --tilt: 0deg; --zoom: 1; padding: 6px; }
|
||||
.chrome { display: none; }
|
||||
.bento { grid-template-columns: minmax(0, 1fr); }
|
||||
.tile.wide { grid-column: auto; }
|
||||
.keys { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||