mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 12:59:13 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Agents produce artifacts: documents, files, pictures, video and links. The app shows them in the task thread, in the right panel's Artifacts tab and on the Artifacts page > - The three surfaces grew one at a time. Their artifact presentation is dated and does not match > - A redesign needs a side-by-side comparison of several directions on the real chrome, in both themes, with hover states, before any product code changes > - This pull request adds a backend-free design lab under `ui/src/artifact-directions`: the shipped presentation as the baseline, four exploratory directions, and the MVP V1 kit drawn from the PCLP-Core mocks > - The benefit is a shareable, capturable reference for the artifact redesign that touches no product code path ## Linked Issues or Issue Description No public issue exists. This section follows the feature request template. **Subsystem affected** The web UI (`ui/`): the task thread, the right panel's Artifacts tab and the Artifacts page. The change is a design lab beside the app, not a change to those surfaces. **Problem or motivation** The three artifact surfaces are being redesigned. Designers must compare candidate directions on the real chrome, in dark and light, with hover states, and share a link. No harness for that exists. Figma mocks do not show the real chrome or the themes. **Proposed solution** A separate Vite entry (`ui/artifact-directions.html`) renders static mocks of the three surfaces. A "kit" supplies the artifact components for each surface. Kits: Today (the shipped components fed with fixtures), four directions (1.Solid, 2.Tint, 3.Tactile, 4.Hybrid) and 5.MVP V1 built from the Figma mocks. A toolbar switches kit, surface, theme and hover animation. The state lives in the URL, so any view can be linked and captured. **Alternatives considered** Figma mocks only: no real chrome, no themes, no hover. Storybook stories only: no page-level composition and no shareable static build. The lab keeps two Storybook stories as a secondary view. **Roadmap alignment** The lab supports the "Artifacts & Work Products" theme in `ROADMAP.md`. It is a design reference. It is not a product feature and it does not ship in the app bundle. **Additional context** The lab builds with its own config (`ui/vite.artifact-directions.config.mjs`). The app's `vite build` does not include it. A build of this branch is live at https://paperclip-artifact-directions.vercel.app. That preview also carries two surfaces that are not in this pull request (a media preview overlay and a panel closeup); they are parked on a separate branch. ## What Changed - Add the lab entry, its Vite config and the shell: `ui/artifact-directions.html`, `ui/vite.artifact-directions.config.mjs`, `ui/src/artifact-directions/` (static frames for the thread, the panel and the Artifacts page, a `fetch` stub for the lab's own page, fixtures, the kit contract, URL state, a sticky two-row toolbar). - Add the Today kit: the shipped artifact components, fed with the lab's fixtures. - Add four directions, each with its own `tokens.css` scoped to `[data-direction]`: 1.Solid, 2.Tint, 3.Tactile, 4.Hybrid. - Add the 5.MVP V1 kit from the PCLP-Core mocks: Artifacts page stacks and single tiles, the right panel's row list, thread rows grouped inline when one message produced several, a hover animation (the backing pages turn 5°, the artwork rises 6px), light-mode values, Paper Mono for the task pill. - Set Paper Mono for every monospace run in the lab (`lab.css`), from the face and `--font-paper-mono` that `index.css` declares. - Add two Storybook stories under `design-proposals` (`artifact-directions-compare`, `artifact-directions-hybrid`), annotated for the Storybook catalog as proposed prototypes on fixtures, and a capture script (`shot.cjs`). - Add two `.claude/launch.json` entries: the lab's dev server and its static preview. - Add Vitest coverage for the lab's logic: URL state, the API stub, the kit's kind treatment, type labels and media kinds, its date labels, print fitting and the thread's inline grouping (six files). ## Verification - Run `cd ui && npx vitest run src/artifact-directions`. The six lab test files pass (31 tests). - Run `cd ui && npx tsc -p tsconfig.json --noEmit`. The output is clean apart from the five pre-existing `PluginOrganizationSwitcher` errors. - Run `node scripts/check-token-gates.mjs`, `node scripts/sync-agent-palette-tokens.mjs --check` and `node scripts/sync-cliplab-character.mjs --check`. All gates are clean. The lab directory is outside the token-gated paths; its tokens are lab-local. - Start the lab: `cd ui && npx vite --config vite.artifact-directions.config.mjs --port 5180`. Open `http://localhost:5180/`. The page lands on 5.MVP V1's Overview in dark mode with animation on. Switch kit, surface and theme in the toolbar; the URL follows. - Open `http://localhost:5180/?d=d5-v1-mvp&s=gallery` and hover a stack: the two backing pages turn and the first page rises. Open `?s=thread` and `?s=panel` for the rows. - Build the lab: `cd ui && ARTIFACT_DIRECTIONS_OUT_DIR=/tmp/artifact-lab npx vite build --config vite.artifact-directions.config.mjs`. - Headless checks ran against the dev server during the work: toolbar labels, defaults and keys; thread and panel row geometry (58px rows, 8px and 12px gaps); the hover animation (5° turn, 6px rise, one easing curve); pill fills per theme; Paper Mono resolution on every monospace run. The measured values are in the kit's doc comments. - Run `node scripts/storybook-catalog.mjs --check` (the check `ui typecheck` runs first): the catalog accepts both stories. - Not run: the repo-wide `pnpm -r typecheck` (it needs the Rust toolchain for `paperclip-runner`) and `pnpm build`. The change adds files under `ui/src/artifact-directions` and `ui/storybook/stories/design-proposals` and edits `.claude/launch.json`; it changes no shipped module. CI runs the full Vitest suite. ## Risks - Low risk for the product. The app bundle does not include the lab. The lab's `fetch` stub installs only on the lab's own page. - Storybook gains two stories; the Storybook build includes them. ## Model Used - Claude Fable 5.1 by Anthropic, model ID `claude-fable-5-1`, used through Claude Code in the desktop app. Extended reasoning was on. Capabilities used: tool use (shell, file edits, a headless Playwright browser for measurements and captures, the Figma MCP server to read the mocks) and image input for mock screenshots. The context window is the Claude Code session's; its exact size is not surfaced to the session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above (none found for "artifact directions lab", "artifacts redesign", "artifacts tab design") - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass (the lab's Vitest files, the UI typecheck and the token gates; CI runs the full suite) - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes (no product behaviour or command changed; the lab documents itself in `kit.ts` and the frames) - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
130 lines
4.6 KiB
JavaScript
130 lines
4.6 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { defineConfig } from "vite";
|
|
import react from "@vitejs/plugin-react";
|
|
import tailwindcss from "@tailwindcss/vite";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
/**
|
|
* The artifact-directions lab: a backend-free page that renders artifact
|
|
* designs for the thread, the task side panel and the Artifacts page inside
|
|
* static mocks of their real surroundings (`src/artifact-directions/`).
|
|
*
|
|
* Dev (any free port; several servers can run side by side):
|
|
*
|
|
* cd ui && npx vite --config vite.artifact-directions.config.mjs --port <p> --strictPort
|
|
*
|
|
* then open http://127.0.0.1:<p>/ (or /artifact-directions.html).
|
|
*
|
|
* Build (this page alone, landed as index.html):
|
|
*
|
|
* cd ui && npx vite build --config vite.artifact-directions.config.mjs
|
|
*
|
|
* Invoked directly rather than through a package.json script, as
|
|
* `vite.flow-preview.config.mjs` is and for the same reason.
|
|
*
|
|
* Three things here are load-bearing:
|
|
*
|
|
* - No `server.proxy`. The app's config forwards `/api` to whatever listens on
|
|
* :3100; the lab answers every `/api/` fetch from a stub in its entry and
|
|
* must never reach a real instance.
|
|
* - `appType: "mpa"` plus the root rewrite. The Vite root also holds the app's
|
|
* `index.html`; with the default SPA fallback `/` would boot the real app,
|
|
* which calls `/api` and registers the service worker on this origin.
|
|
* - A cache directory per port. The default `node_modules/.vite` is shared, and
|
|
* concurrent dev servers race on its dependency metadata.
|
|
*/
|
|
|
|
const ENTRY = "artifact-directions.html";
|
|
const DEFAULT_PORT = 5180;
|
|
|
|
/** A config cannot see the resolved CLI options, so the flag is read from argv. */
|
|
function portFromArgv() {
|
|
const argv = process.argv;
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
if (argv[i] === "--port" && argv[i + 1]) return Number(argv[i + 1]);
|
|
if (argv[i].startsWith("--port=")) return Number(argv[i].slice("--port=".length));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const PORT = portFromArgv() || Number(process.env.ARTIFACT_DIRECTIONS_PORT) || DEFAULT_PORT;
|
|
const OUT_DIR = process.env.ARTIFACT_DIRECTIONS_OUT_DIR
|
|
? path.resolve(process.env.ARTIFACT_DIRECTIONS_OUT_DIR)
|
|
: path.resolve(__dirname, "dist-artifact-directions");
|
|
|
|
/**
|
|
* Live reload polls the lab's own files instead of waiting for native file
|
|
* events. When macOS runs out of FSEvents watchers — every long-running watch
|
|
* process on the machine holds some — `fs.watch` on a directory fails with
|
|
* EMFILE, and Vite's watcher, which uses FSEvents whenever the module loads,
|
|
* goes silent without an error: edits never reach the page. Polling only the
|
|
* lab (a few hundred files) keeps the cost negligible.
|
|
*/
|
|
const LAB_WATCH_PATHS = [
|
|
path.resolve(__dirname, "src/artifact-directions"),
|
|
path.resolve(__dirname, "src/index.css"),
|
|
path.resolve(__dirname, ENTRY),
|
|
];
|
|
|
|
function outsideLab(watchedPath) {
|
|
const target = path.resolve(__dirname, String(watchedPath));
|
|
if (LAB_WATCH_PATHS.some((lab) => target === lab || target.startsWith(lab + path.sep))) return false;
|
|
// Keep the directories on the way down to the lab, or the watcher never reaches it.
|
|
return !LAB_WATCH_PATHS.some((lab) => lab.startsWith(target + path.sep));
|
|
}
|
|
|
|
const entryAsRoot = {
|
|
name: "artifact-directions-entry-as-root",
|
|
configureServer(server) {
|
|
server.middlewares.use((req, _res, next) => {
|
|
const [pathname, query] = (req.url || "").split("?");
|
|
if (pathname === "/" || pathname === "/index.html") {
|
|
req.url = `/${ENTRY}${query ? `?${query}` : ""}`;
|
|
}
|
|
next();
|
|
});
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Renamed on disk in `closeBundle`, not rekeyed in `generateBundle`: Vite's
|
|
* HTML plugin emits the document after user plugins have had that turn.
|
|
*/
|
|
const htmlAsIndex = {
|
|
name: "artifact-directions-html-as-index",
|
|
closeBundle() {
|
|
const built = path.resolve(OUT_DIR, ENTRY);
|
|
if (!fs.existsSync(built)) return;
|
|
fs.renameSync(built, path.resolve(OUT_DIR, "index.html"));
|
|
},
|
|
};
|
|
|
|
export default defineConfig({
|
|
appType: "mpa",
|
|
plugins: [react(), tailwindcss(), entryAsRoot, htmlAsIndex],
|
|
resolve: {
|
|
alias: {
|
|
"@": path.resolve(__dirname, "./src"),
|
|
lexical: path.resolve(__dirname, "./node_modules/lexical/dist/Lexical.mjs"),
|
|
},
|
|
},
|
|
cacheDir: path.resolve(__dirname, `node_modules/.vite-artifact-directions-${PORT}`),
|
|
server: {
|
|
host: "127.0.0.1",
|
|
port: PORT,
|
|
strictPort: true,
|
|
watch: { usePolling: true, interval: 250, ignored: outsideLab },
|
|
},
|
|
build: {
|
|
outDir: OUT_DIR,
|
|
emptyOutDir: true,
|
|
minify: "esbuild",
|
|
rollupOptions: {
|
|
input: path.resolve(__dirname, ENTRY),
|
|
},
|
|
},
|
|
});
|