Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
118ea38
feat(profiles): Add schema-driven profile authoring and query browser…
moshloop Aug 11, 2026
9666c63
feat(ui): add cards display for object arrays
moshloop Aug 11, 2026
d55f1e1
feat(ui): add profile and query browser demos
moshloop Aug 11, 2026
0b70a24
fix(profiles): inline workspace layout styles for consumers
moshloop Aug 11, 2026
28e9c07
feat(ui): add unit-aware inputs and validated profile sampling
moshloop Aug 11, 2026
d5b1ea9
fix(ui): harden profile slugs and clarify error report controls
moshloop Aug 12, 2026
abd8a9c
build(storybook): Prebundle Monaco editor dependencies
moshloop Aug 12, 2026
efacc42
feat(ui): Add AccordionList and modernize array form controls
moshloop Aug 13, 2026
cc2d4cf
feat(ui): add on-demand query details and async download preparation
moshloop Aug 13, 2026
439710b
feat(ui): Consolidate Clicky exports into a scoped dialog
moshloop Aug 13, 2026
1e33327
refactor(profiles): organize profile exports into domain barrels
moshloop Aug 14, 2026
dc71a62
feat(expressions): add gomplate language support and expression playg…
moshloop Aug 14, 2026
a40265d
feat(profiles): add interactive profile editing and processor previews
moshloop Aug 14, 2026
2c6234c
feat(ui): add richer list controls and namespace-aware workload selec…
moshloop Aug 14, 2026
39e2cae
build(workspace): migrate to Tailwind CSS 4 and automate expressions …
moshloop Aug 14, 2026
87033b6
feat(ui): Expose persistent chat controls and RPC-aware chat layers
moshloop Aug 14, 2026
92ed6d6
feat(ui): Add richer filters, exports, and operation diagnostics
moshloop Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .gavel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ lint:
pre:
- name: build
run: pnpm --filter @flanksource/clicky-ui run build
- name: build-expressions
run: pnpm --filter @flanksource/expressions run build
- name: install-storybook-browser
run: pnpm --filter storybook exec playwright install chromium
procfile: {}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ jobs:
- run: vp install
- name: Build library
run: pnpm --filter @flanksource/clicky-ui run build
- name: Build expressions
run: pnpm --filter @flanksource/expressions run build
- name: Build Storybook
run: pnpm --filter storybook exec storybook build --disable-telemetry
- name: Build kitchen-sink
Expand Down
50 changes: 37 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ jobs:
runs-on: ubuntu-latest
# Skip the auto-bump commit so the workflow doesn't loop on itself.
if: "!startsWith(github.event.head_commit.message, 'chore(release):')"
strategy:
# Serially: each package commits its own version bump to main, and two
# jobs pushing to the same protected branch would race.
max-parallel: 1
fail-fast: false
matrix:
include:
- name: "@flanksource/clicky-ui"
directory: packages/ui
tag: clicky-ui
- name: "@flanksource/expressions"
directory: packages/expressions
tag: expressions
steps:
# Authenticate as a GitHub App so the bump commit/tag can be pushed to the
# protected main branch. The App must be on the branch ruleset's bypass list
Expand Down Expand Up @@ -50,51 +63,62 @@ jobs:

- name: Bump patch version
id: bump
working-directory: packages/ui
working-directory: ${{ matrix.directory }}
# Derive the next version from the highest version already published on
# npm, not from package.json/git — they can drift when a prior release
# publishes but its bump commit never lands back on main.
env:
TAG_PREFIX: ${{ matrix.tag }}
run: |
name=$(node -p "require('./package.json').name")
published=$(npm view "$name" versions --json \
published=$(npm view "$name" versions --json 2>/dev/null \
| jq -r 'if type == "array" then .[] else . end' \
| sort -V | tail -1)
if [ -z "$published" ]; then
echo "Could not determine the highest published version for $name" >&2
exit 1
# Never published. The version in package.json is the intended
# first release, so take it as-is rather than bumping past it.
new_version="v$(node -p "require('./package.json').version")"
echo "First publish of $name at ${new_version#v}"
else
echo "Highest published version: $published"
npm version "$published" --no-git-tag-version --allow-same-version >/dev/null
new_version=$(npm version patch --no-git-tag-version)
fi
echo "Highest published version: $published"
npm version "$published" --no-git-tag-version --allow-same-version >/dev/null
new_version=$(npm version patch --no-git-tag-version)
echo "version=${new_version#v}" >> "$GITHUB_OUTPUT"
echo "tag=clicky-ui@${new_version#v}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG_PREFIX}@${new_version#v}" >> "$GITHUB_OUTPUT"

- name: Build library
run: pnpm --filter @flanksource/clicky-ui run build
run: pnpm --filter ${{ matrix.name }} run build

- name: Publish to npm
working-directory: packages/ui
working-directory: ${{ matrix.directory }}
env:
NPM_CONFIG_PROVENANCE: "true"
run: pnpm publish --access public --no-git-checks

- name: Commit, tag, and push
env:
NAME: ${{ matrix.name }}
DIRECTORY: ${{ matrix.directory }}
VERSION: ${{ steps.bump.outputs.version }}
TAG: ${{ steps.bump.outputs.tag }}
run: |
git add packages/ui/package.json
git commit -m "chore(release): @flanksource/clicky-ui@${VERSION}"
git add "${DIRECTORY}/package.json"
git commit -m "chore(release): ${NAME}@${VERSION}"
git tag "${TAG}"
# Rebase before pushing: an earlier matrix entry may have landed its
# own bump commit on main since this job checked out.
git pull --rebase origin "${GITHUB_REF_NAME}"
git push origin "HEAD:${GITHUB_REF_NAME}"
git push origin "${TAG}"

- name: Create GitHub Release
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
NAME: ${{ matrix.name }}
VERSION: ${{ steps.bump.outputs.version }}
TAG: ${{ steps.bump.outputs.tag }}
run: |
gh release create "${TAG}" \
--title "@flanksource/clicky-ui@${VERSION}" \
--title "${NAME}@${VERSION}" \
--generate-notes
102 changes: 102 additions & 0 deletions .github/workflows/vendor-lang.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Vendor gomplate languages

# The tokenizers under packages/expressions/src/lang are generated from cel-go's
# ANTLR grammar, text/template's lexer and gomplate's live function registries.
# Only a Go toolchain can read any of that, so this job runs gomplate's own
# generator and opens a PR when the result changes.
on:
schedule:
# Weekly. gomplate's function surface moves with releases, not with commits,
# so anything more frequent is just noise in the PR list.
- cron: "0 6 * * 1"
workflow_dispatch:
inputs:
ref:
description: gomplate ref to vendor from
required: false
default: main
pull_request:
# Only when the vendored tree is touched. Regenerating needs Go and a clone
# of gomplate, which is not worth paying for on every unrelated PR.
paths:
- packages/expressions/src/lang/**
- packages/expressions/VENDOR
- packages/expressions/scripts/vendor-lang.ts

permissions:
contents: read

jobs:
verify:
# Catches a hand-edit under src/lang, which would be silently overwritten by
# the next scheduled vendoring.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- run: corepack enable
- name: Install Vite+
run: curl -fsSL https://vite.plus | bash
- name: Add Vite+ to PATH
run: echo "$HOME/.vite-plus/bin" >> "$GITHUB_PATH"
- run: vp install
- name: Check the vendored tree is generated, not hand-edited
working-directory: packages/expressions
run: pnpm vendor:check


vendor:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
# A GitHub App token, matching release.yml: the default GITHUB_TOKEN
# cannot open a PR that triggers other workflows.
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.FLANKSOURCE_APP_ID }}
private-key: ${{ secrets.FLANKSOURCE_APP_SECRET }}

- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}

- uses: actions/setup-go@v5
with:
go-version: stable

- run: corepack enable
- name: Install Vite+
run: curl -fsSL https://vite.plus | bash
- name: Add Vite+ to PATH
run: echo "$HOME/.vite-plus/bin" >> "$GITHUB_PATH"
- run: vp install

- name: Vendor
working-directory: packages/expressions
run: pnpm vendor --ref "${{ inputs.ref || 'main' }}"

# The vendored tree is source: it has to typecheck and its tests have to
# pass before a PR is worth anyone's attention.
- name: Verify
run: pnpm --filter @flanksource/expressions run check && pnpm --filter @flanksource/expressions run test

- name: Open a pull request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ steps.app-token.outputs.token }}
branch: vendor/gomplate-lang
title: "chore(expressions): vendor gomplate language definitions"
commit-message: "chore(expressions): vendor gomplate language definitions"
body: |
Regenerated `packages/expressions/src/lang` from gomplate.
See `packages/expressions/VENDOR` for the commit it came from.
add-paths: |
packages/expressions/src/lang
packages/expressions/VENDOR
3 changes: 1 addition & 2 deletions apps/kitchen-sink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@
"shiki": "catalog:"
},
"devDependencies": {
"@tailwindcss/vite": "catalog:",
"@testing-library/react": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"autoprefixer": "catalog:",
"jsdom": "catalog:",
"postcss": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
Expand Down
6 changes: 0 additions & 6 deletions apps/kitchen-sink/postcss.config.js

This file was deleted.

11 changes: 11 additions & 0 deletions apps/kitchen-sink/src/demo-catalog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ComponentType } from "react";
import { AccordionListDemo } from "./demos/AccordionListDemo";
import { SwitchersDemo } from "./demos/SwitchersDemo";
import { ButtonDemo } from "./demos/ButtonDemo";
import { IconDemo } from "./demos/IconDemo";
Expand Down Expand Up @@ -61,6 +62,8 @@ import { ToastDemo } from "./demos/ToastDemo";
import { CommentsDemo } from "./demos/CommentsDemo";
import { GavelStylingComparisonDemo } from "./demos/GavelStylingComparisonDemo";
import { HierarchicalLookupDemo } from "./demos/HierarchicalLookupDemo";
import { ProfilesDemo } from "./demos/ProfilesDemo";
import { QueryBrowserDemo } from "./demos/QueryBrowserDemo";
import { TourDemo } from "./demos/TourDemo";
import {
type StaticIconComponent,
Expand Down Expand Up @@ -257,6 +260,12 @@ export const DEMO_GROUPS: DemoGroup[] = [
component: FormFieldsDemo,
icon: UiForm,
},
{
id: "accordion-list",
label: "AccordionList",
component: AccordionListDemo,
icon: UiRows,
},
{
id: "hierarchical-lookup",
label: "Hierarchical lookup",
Expand Down Expand Up @@ -431,6 +440,8 @@ export const DEMO_GROUPS: DemoGroup[] = [
{
title: "Clicky-RPC",
items: [
{ id: "query-browser", label: "QueryBrowser", component: QueryBrowserDemo, icon: UiTerminal },
{ id: "profiles", label: "Profiles", component: ProfilesDemo, icon: UiListTree },
{
id: "command-form",
label: "CommandForm",
Expand Down
114 changes: 114 additions & 0 deletions apps/kitchen-sink/src/demos/AccordionListDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useState } from "react";
import { AccordionList, Badge } from "@flanksource/clicky-ui";
import { DemoSection } from "./Section";

type Route = { path: string; method: string; upstream: string };

const ROUTES: Route[] = [
{ path: "/api/v1/users", method: "GET", upstream: "users-svc:8080" },
{ path: "/api/v1/events", method: "POST", upstream: "events-svc:8080" },
{ path: "/api/v1/health", method: "GET", upstream: "gateway:8080" },
];

type Middleware = { name: string; phase: string; config: string };

const MIDDLEWARES: Middleware[] = [
{ name: "rate-limit", phase: "pre-auth", config: "100 req/min per token" },
{ name: "jwt-verify", phase: "auth", config: "RS256, 5m clock skew" },
{ name: "access-log", phase: "post-response", config: "json, sampled 1:10" },
];

function Field({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (next: string) => void;
}) {
return (
<label className="flex items-center gap-density-2 text-sm">
<span className="w-24 shrink-0 text-muted-foreground">{label}</span>
<input
className="h-8 flex-1 rounded-md border border-input bg-background px-2 text-sm"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</label>
);
}

export function AccordionListDemo() {
const [routes, setRoutes] = useState(ROUTES);
const [middlewares, setMiddlewares] = useState(MIDDLEWARES);

return (
<DemoSection
id="accordion-list"
title="AccordionList"
description="A list of items collapsed to one row each, expanding one at a time. renderHeader / renderBody are the only content you supply; reorder, duplicate, remove and the add row are each opt-in."
>
<p className="text-xs text-muted-foreground">
Full editor — <code>allowReorder allowDuplicate allowRemove</code> plus{" "}
<code>onCreate</code>. Arrow keys, Home and End rove across the rows and onto the add row.
</p>
<AccordionList<Route>
items={routes}
onChange={setRoutes}
summary={routes.length === 1 ? "1 route" : `${routes.length} routes`}
itemLabel={({ item }) => item.path || "new route"}
allowReorder
allowDuplicate
allowRemove
onCreate={() => ({ path: "", method: "GET", upstream: "" })}
addLabel="Add route"
addDescription="A route forwards one path to one upstream service."
renderHeader={({ item, index }) => (
<>
{/* The header renders inside the disclosure button, so nothing in it
may be interactive — a copyable Badge would be a nested button. */}
<Badge variant="outline" clickToCopy={false}>
{item.method}
</Badge>
<span className="shrink-0 text-sm font-medium">
{item.path || `Route ${index + 1}`}
</span>
<code className="truncate font-mono text-xs text-muted-foreground">{item.upstream}</code>
</>
)}
renderBody={({ item, onChange }) => (
<div className="flex flex-col gap-density-2">
<Field label="Path" value={item.path} onChange={(path) => onChange({ ...item, path })} />
<Field
label="Upstream"
value={item.upstream}
onChange={(upstream) => onChange({ ...item, upstream })}
/>
</div>
)}
/>

<p className="text-xs text-muted-foreground">
Reorder only — no add row, no duplicate, no delete. The same component, different opt-ins.
</p>
<AccordionList<Middleware>
items={middlewares}
onChange={setMiddlewares}
allowReorder
itemLabel={({ item }) => item.name}
renderHeader={({ item }) => (
<span className="shrink-0 text-sm font-medium">{item.name}</span>
)}
renderBody={({ item }) => (
<dl className="grid grid-cols-[6rem_1fr] gap-x-density-3 text-sm">
<dt className="text-muted-foreground">Phase</dt>
<dd>{item.phase}</dd>
<dt className="text-muted-foreground">Config</dt>
<dd className="font-mono text-xs">{item.config}</dd>
</dl>
)}
/>
</DemoSection>
);
}
Loading
Loading