Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ test-results
playwright-report
.vercel
.cursor/debug.log
.cursor
.cursor
meta.json
6 changes: 1 addition & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pnpm typecheck # runs type checking
pnpm format
```

## Cursor Cloud specific instructions
## Development instructions

This is a pnpm + Turborepo monorepo (19 packages under `packages/`). No external services (databases, Docker, etc.) are required.

Expand All @@ -117,10 +117,6 @@ The root `package.json` has `pnpm.onlyBuiltDependencies` configured for `@parcel

E2E tests (`pnpm test` at root) run Playwright against the `e2e-playground` Vite dev server on port 5175 (auto-started by the Playwright config). Chromium must be installed: `npx --prefix packages/react-grab playwright install chromium --with-deps`.

### Known flaky test

`e2e/history-items.spec.ts` > "should reposition when toolbar is dragged to top edge" intermittently times out in headless CI environments. This is a pre-existing issue.

### Key commands reference

See root `package.json` scripts and `CONTRIBUTING.md` for the full list. Quick reference:
Expand Down
5 changes: 3 additions & 2 deletions packages/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@solidjs/web": "2.0.0-beta.3",
"react-grab": "workspace:*",
"solid-js": "^1.9.10"
"solid-js": "2.0.0-beta.3"
},
"devDependencies": {
"@babel/core": "^7.28.5",
"@babel/preset-typescript": "^7.28.5",
"babel-preset-solid": "^1.9.10",
"babel-preset-solid": "2.0.0-beta.3",
"esbuild-plugin-babel": "^0.2.3",
"tsup": "^8.2.4"
}
Expand Down
220 changes: 120 additions & 100 deletions packages/design-system/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-expect-error - CSS imported as text via tsup loader
import cssText from "react-grab/dist/styles.css";
import { render } from "solid-js/web";
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
import { render } from "@solidjs/web";
import { createSignal, For, onCleanup, onSettled, Show } from "solid-js";
import { SelectionLabel } from "react-grab/src/components/selection-label/index.js";
import { ContextMenu } from "react-grab/src/components/context-menu.js";
import { ToolbarContent } from "react-grab/src/components/toolbar/toolbar-content.js";
Expand Down Expand Up @@ -2721,14 +2721,13 @@ const FpsMeter = (props: FpsMeterProps) => {
animationFrameId = requestAnimationFrame(measureFps);
};

onMount(() => {
onSettled(() => {
animationFrameId = requestAnimationFrame(measureFps);
});

onCleanup(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
return () => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
};
});

return (
Expand Down Expand Up @@ -3110,19 +3109,22 @@ const DesignSystemGrid = () => {
</span>
<div style={gridStyle()}>
<For each={starredStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3134,19 +3136,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>Flows</span>
<div style={gridStyle()}>
<For each={flowStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3158,19 +3163,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>Selection Label</span>
<div style={gridStyle()}>
<For each={labelStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3182,19 +3190,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>Context Menu (Right-Click)</span>
<div style={gridStyle()}>
<For each={contextMenuStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3206,19 +3217,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>Toolbar</span>
<div style={gridStyle()}>
<For each={toolbarStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3230,19 +3244,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>History Dropdown</span>
<div style={gridStyle()}>
<For each={historyDropdownStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand All @@ -3254,19 +3271,22 @@ const DesignSystemGrid = () => {
<span style={sectionTitleStyle()}>Agent States</span>
<div style={gridStyle()}>
<For each={agentLabelStates()}>
{(state) => (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
)}
{(stateAccessor) => {
const state = stateAccessor();
return (
<StateCard
state={state}
theme={theme()}
getBounds={() => getBoundsForCell(state.id)}
registerCell={(element) => registerCell(state.id, element)}
onRefresh={createRefreshHandler(state.id)}
getTargetDisplayText={() => getTargetDisplayText(state)}
isStarred={isStarred(state.id)}
onToggleStar={() => handleToggleStar(state.id)}
isScrambled={isScrambled()}
/>
);
}}
</For>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/design-system/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const options: Options = {
".css": "text",
},
minify: process.env.NODE_ENV === "production",
noExternal: ["solid-js", /^react-grab\/src/, "react-grab/dist/styles.css"],
noExternal: ["solid-js", "@solidjs/web", /^react-grab\/src/, "react-grab/dist/styles.css"],
outDir: "./dist",
platform: "browser",
sourcemap: false,
Expand Down
6 changes: 3 additions & 3 deletions packages/react-grab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@
"dependencies": {
"@medv/finder": "^4.0.2",
"@react-grab/cli": "workspace:*",
"@solidjs/web": "2.0.0-beta.3",
"bippy": "^0.5.32",
"element-source": "^0.0.3",
"solid-js": "^1.9.10"
"solid-js": "2.0.0-beta.3"
},
"devDependencies": {
"@babel/core": "^7.28.5",
Expand All @@ -111,13 +112,12 @@
"@tailwindcss/cli": "^4.1.17",
"@types/node": "^20.19.23",
"@types/react": "^19.2.11",
"babel-preset-solid": "^1.9.10",
"babel-preset-solid": "2.0.0-beta.3",
"clsx": "^2.1.1",
"concurrently": "^9.1.2",
"esbuild-plugin-babel": "^0.2.3",
"oxlint": "^1.42.0",
"publint": "^0.2.12",
"tailwind-merge": "^2.5.5",
"tailwindcss": "^4.1.0",
"tsup": "^8.2.4"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/react-grab/src/components/clear-history-prompt.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Show, onMount, onCleanup } from "solid-js";
import { Show, onSettled } from "solid-js";
import type { Component } from "solid-js";
import type { DropdownAnchor } from "../types.js";
import { DROPDOWN_EDGE_TRANSFORM_ORIGIN, Z_INDEX_LABEL } from "../constants.js";
Expand All @@ -24,7 +24,7 @@ export const ClearHistoryPrompt: Component<ClearHistoryPromptProps> = (
() => props.position,
);

onMount(() => {
onSettled(() => {
dropdown.measure();
const unregisterOverlayDismiss = registerOverlayDismiss({
isOpen: () => Boolean(props.position),
Expand All @@ -33,10 +33,10 @@ export const ClearHistoryPrompt: Component<ClearHistoryPromptProps> = (
shouldIgnoreInputEvents: true,
});

onCleanup(() => {
return () => {
dropdown.clearAnimationHandles();
unregisterOverlayDismiss();
});
};
});

return (
Expand Down
Loading
Loading