Skip to content

feat(appkit): /ui visual variant picker#476

Open
IamGalymzhan wants to merge 15 commits into
mainfrom
feat/ui-variants
Open

feat(appkit): /ui visual variant picker#476
IamGalymzhan wants to merge 15 commits into
mainfrom
feat/ui-variants

Conversation

@IamGalymzhan

@IamGalymzhan IamGalymzhan commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

/ui variant selector skill

Document

This PR is adding a new feature of /ui-variants skill, that makes agent create several design versions of the UI component and allows user to select it live on the web page, while agent could read it on the next turn and implement selected degisn.

How to test

  1. Wire up the skill pnpm --filter=dev-playground exec appkit skills install
  2. Use /ui-variants command via Isaac/Claude Code and type want you want to build in dev-playground
  3. Once agent completes go to the page and select needed variants
    • You can see choices landed in the node_modules/.databricks/appkit/appkit-ui-choices.jsonl file
  4. Tell agent that you have selected variants that you liked
  5. Confirm everything is done (No component, selected the right design)

How should it work for user

  1. Agent skills is installed on setup (via symlink to the project's .claude/ folder)
  2. /ui-variants skill is available right away

@github-actions

Copy link
Copy Markdown
Contributor

🔬  Run evals on this PR  ·  Go to Evals Monitor →

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 763 KB (+10 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 791 KB (+7.5 KB) 276 KB (+3.4 KB)
Type declarations 285 KB (+329 B) 97 KB (+127 B)
Source maps 1.5 MB (+12 KB) 515 KB (+5.0 KB)
Other 11 KB 3.7 KB
Total 2.6 MB (+20 KB) 892 KB (+8.5 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 86 KB (+927 B) 2.5 KB 89 KB (+927 B) external 281 KB (+2.6 KB)
./beta 40 KB 231 B 40 KB external 119 KB
./type-generator 19 KB 0 B 19 KB external 54 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 82 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 30 KB
./beta databricks.js initial 5.8 KB
./beta service-context.js initial 3.2 KB
./beta client-options.js initial 220 B
./beta databricks.js lazy 128 B
./beta index.js lazy 103 B
./type-generator index.js initial 19 KB

@databricks/appkit-ui

npm tarball (packed): 305 KB (+9.8 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 359 KB (+9.4 KB) 119 KB (+3.6 KB)
Type declarations 205 KB (+4.5 KB) 74 KB (+2.1 KB)
Source maps 685 KB (+16 KB) 224 KB (+6.2 KB)
CSS 16 KB 3.3 KB
Total 1.2 MB (+30 KB) 421 KB (+12 KB)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 4.3 KB 49 KB 54 KB 208 KB 12 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 429 KB (+1.4 KB) 49 KB 478 KB (+1.4 KB) 1.3 MB (+136 B) 168 KB (+4.3 KB)
./react/beta 20 B 0 B 20 B 0 B 0 B

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 4.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 427 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 20 B

@IamGalymzhan
IamGalymzhan marked this pull request as ready for review July 17, 2026 12:18
@IamGalymzhan
IamGalymzhan requested a review from a team as a code owner July 17, 2026 12:18
Build a piece of app UI in several variants, pick one live in the running app,
then finalize the chosen variant into source.

- `<Variants>`/`<Variant>` (appkit-ui): inline switcher — hover-revealed
  controls, tick confirm, `accent` and `layout` props — that degrades to a
  plain viewer when the recorder is absent. The block identifier prop is
  `blockId` (a semantic key, not a DOM id).
- `uiVariants()` recorder plugin (appkit): POST /api/ui-variants/confirm,
  dev-gated (403), keyed upsert into
  node_modules/.databricks/appkit/.appkit-ui-choices.jsonl via a swappable
  ChoiceSink (default FileChoiceSink). Register conditionally so it never
  mounts in production.
- ast-grep lint rule flagging any <Variants> left in source before deploy.
- dev-playground demo route + nav.
- `/ui` command + guidance reference driving the author → pick → finalize loop.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Introduce a declarative `devOnly` field on the plugin manifest. When set,
createApp registers the plugin only when NODE_ENV === "development"; in any
other environment it is skipped before resource collection (not constructed,
no routes, resources not validated). This moves the dev-only guarantee from
an app-author-written NODE_ENV ternary into the framework itself.

Mark the ui-variants plugin devOnly and simplify the dev-playground
registration to a plain uiVariants() call. The in-handler NODE_ENV 403 is
retained as fail-safe defense against mount-anyway paths.

Adds the field to the Zod schema (source of truth) and PluginManifest type,
regenerates the published JSON schema, documents it in the manifest guide,
and covers development/production/test/unset in bootstrap tests.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Signed-off-by: Galymzhan <zhangazy2004@gmail.com>

@calvarjorge calvarjorge left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Took a first look and left some comments

Comment thread .claude/commands/ui.md Outdated
@@ -0,0 +1,106 @@
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the place where we want to place the skill? Maybe yes, but there is a difference with the rest of the skills in the dir -- the other skills are to be used by appkit developers, while this skilled is meant to be installed and used by appkit users

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the skill into the appkit itself under the skills folder, with installation via appkit cli command (appkit skills install)

Comment thread .claude/commands/ui.md Outdated
@@ -0,0 +1,106 @@
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to name it /ui? Can we align on this to avoid changing it later? Maybe this is to broad, other option could be /appkit-ui-variants

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am proposing a name /ui-variants

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is /appkit-ui-variants now

Comment thread packages/appkit-ui/src/react/ui-variants/index.ts
IamGalymzhan and others added 7 commits July 20, 2026 16:19
Match the appkit plugin dir name for cross-package consistency. Files and the
public API are unchanged; only the internal directory moves.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Consolidate the /ui command and its variants guidance into a single
self-contained skill shipped inside @databricks/appkit (skills/ui.md),
and add an `appkit skills install` CLI command that symlinks shipped
skills into a consuming project's .claude/commands/ (--copy for
git/deploy portability, --dir, --force). `appkit setup --write` now
auto-installs skills, and the dist tarball ships the skills/ dir.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Align the shipped skill's command name with the ui-variants naming used
across the branch (plugin, dir, route). The install command derives the
slash-command name from the filename, so this makes it install as
/ui-variants.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
createApp now skips devOnly plugins automatically, so the skill just says to
add uiVariants() to the plugin list.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Installs as the /appkit-ui-variants slash command (name derived from the file).

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
const [active, setActive] = useState(0);
const [confirmState, setConfirmState] = useState<ConfirmState>("idle");

if (variants.length === 0) return null;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about a small prod safety net right after the length check (kept below the hooks):

Suggested change
if (variants.length === 0) return null;
if (variants.length === 0) return null;
if (!import.meta.env.DEV) return <>{variants[0]}</>;

so if a block ever ships without being finalized, users see real content instead of the picker chrome — variants[0] avoids the blank section that returning null would leave. not a security thing (the recorder is already server-gated), just belt-and-suspenders on top of the no-variants-in-prod lint. import.meta.env.DEV to match what we already do in use-query-hmr.ts 😄

non-blocking, just a thought

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for noting that, added it

Comment thread packages/appkit/src/index.ts Outdated
Comment on lines +88 to +91
type ChoiceSink,
FileChoiceSink,
type UiChoiceRecord,
uiVariants,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove the export

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

IamGalymzhan and others added 4 commits July 24, 2026 09:44
createApp now injects the ui-variants recorder before the dev-only
filter runs, so it is always alive in development and always stripped
in production through the same guard as any devOnly plugin. Neither the
app author nor their coding agent needs to register it by hand.

Also drops the unused ChoiceSink abstraction (folded into an internal
FileChoiceStore) and removes uiVariants and the sink types from the
public API surface, since manual registration is no longer a path.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
If a <Variants> block ever ships without being finalized, users saw the
dev-only picker chrome. In a production build the component now drops the
chrome and renders the first variant as plain content — a cosmetic safety
net on top of the no-variants-in-prod lint; the recorder is already
server-gated. Guard sits below the hooks and uses import.meta.env.DEV to
match use-query-hmr.

Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Signed-off-by: Galymzhan <zhangazy2004@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants