RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Configs/AGENTS.md/kestra-io/kestra

AGENTS.md

ui/AGENTS.md
AGENTS.md

Quality

53/100

Scores the file, not the repository.

Length

4,025 words

29 headings · 9 code blocks

Repository

28k

— · pushed 0 days ago

Last changed

2 days ago

First indexed 2 days ago.
kestra-io/kestra/ui/AGENTS.mdRawGitHub
1# UI Design System Guidelines
2 
3Scope: this file applies to everything under `ui/`. AI coding agents (Claude Code, Cursor, etc.) load this file automatically when working in this directory; humans should treat it as the source of truth for frontend conventions in Kestra.
4 
5The Kestra design system lives at [ui/packages/design-system/](packages/design-system/) and is the **single source of truth** for every visual element of the product — colors, fonts, spacing, buttons, forms, dialogs, tables, charts, and so on. Anything rendered to a user must come from it.
6 
7## What this is, in plain terms
8 
9Think of the design system as the product's **visual vocabulary**:
10 
11- A short list of agreed-upon **colors**, **fonts**, and **spacings** (called *design tokens*).
12- A library of pre-built **components** (`KsButton`, `KsTable`, `KsDialog`, …) that already use those tokens.
13- A guarantee that anything built from these pieces will look right in **light mode and dark mode**, follow accessibility rules, and stay visually consistent with the rest of Kestra.
14 
15If a screen feels "off-brand," looks broken in dark mode, or every page styles the same control differently, it's almost always because someone bypassed the design system. The rules below exist to prevent that.
16 
17Under the hood, the design system wraps Element Plus under the `kel` namespace and globally registers every component with a `Ks*` prefix. You should almost never `import` from `element-plus` directly in `ui/src/`.
18 
19> **Note on `@kestra-io/ui-libs`:** The codebase may still contain imports from `@kestra-io/ui-libs`, the previous shared component library. That repository is sunsetting — all components have been migrated here into `ui/packages/`. Do not add new imports from `@kestra-io/ui-libs`; use `Ks*` components from the design system instead.
20 
21## Golden rules (non-negotiable)
22 
23These rules are what keep the UI maintainable as it grows. Treat any deviation as a bug.
24 
251. **Use a `Ks*` component if one exists.** Check the tables below before writing anything custom or importing from `element-plus`. New screens that mix `<el-button>` and `<KsButton>` are a regression.
262. **Colors come from `--ks-*` tokens. Always.** No hex codes, no `rgb(...)`, no Element Plus tokens (`--el-*`), no Bootstrap variables, no SCSS color variables in component code. If the token you need does not exist, talk to design and add it to `ks-theme-light.scss` / `ks-theme-dark.scss` / `ks-theme-dark-2.scss` — do not pick a one-off color.
273. **Typography comes from `KsText` or typography tokens.** Use `<KsText>` (with `size`, `type`, `tag`, `truncated`, `lineClamp`) for body copy. For headings or one-off needs, use the `$font-family-*` and `$font-size-*` SCSS variables only inside the design-system package — feature code should not redefine them.
284. **No `:deep()` selectors.** Reaching into a child component's internals breaks encapsulation and silently shatters when the design system is upgraded. If you need to style something inside a `Ks*` component, add a prop, a slot, or a CSS variable to the component upstream.
295. **No SCSS variables (`$...`) in feature components.** Use `var(--ks-*)` CSS custom properties inside `<style>` blocks. SCSS variables don't react to dark mode, can't be overridden at runtime, and bind your component to a specific theme. SCSS variables are only acceptable inside `ui/packages/design-system/` itself, in mixins, or for math at build time.
306. **No magic numbers for theme values.** Spacing, radii, font sizes, and shadows must reference tokens or design-system SCSS variables — never `padding: 13px`, never `border-radius: 6px`. For spacing (`padding`/`margin`/`gap`), reach for the `--ks-spacing-*` scale first (`--ks-spacing-1` = 0.25rem, `-2` = 0.5rem, `-3` = 0.75rem, `-4` = 1rem, `-5` = 1.5rem, `-6` = 2rem, `-7` = 2.5rem, `-8` = 3rem, `-10` = 4rem, `-12` = 5rem, `-16` = 6rem; declared in [`ks-tokens.scss`](packages/design-system/src/assets/styles/ks-tokens.scss)). Only fall back to a raw `rem` value when no token fits — never a hardcoded `px` value (`margin: 0 24px` → `margin: 0 var(--ks-spacing-5)`).
317. **Never override Element Plus classes directly.** Don't write `.el-button { ... }` in feature code. If a `Ks*` component is missing a behavior, extend the component in the design system instead of patching CSS at the call site.
328. **Don't fork — extend.** If a `Ks*` component is *almost* what you need, add a prop or a slot to the component in `ui/packages/design-system/`. Copy-pasting the component into your feature folder is forbidden.
339. **Every new `Ks*` component needs a Storybook story and a unit test.** Stories double as living documentation for design and product reviewers.
3410. **i18n keys live with the design system component**, not inside feature code, when they belong to the component (e.g. `KsEmpty`, `KsDurationPicker`). Register them via `registerDesignSystemI18n`.
35 
36## Best practices for keeping the design system healthy
37 
38A design system rots fast if it's treated as a one-time deliverable. Apply these rules every time you touch UI code or review a UI PR.
39 
40### Before you write code
41 
42- Search the component tables and Storybook first. The most common waste in this codebase is rebuilding something that already exists.
43- If you can't find what you need, ask: is this a *missing component* (fix it in the DS) or a *missing prop on an existing component* (extend the DS)? Almost never the answer "build it locally."
44- For anything visible to a user, check both light and dark mode in Storybook before merging.
45 
46### While you write code
47 
48- Build screens by *composing* `Ks*` components. A new feature should read like a list of design-system blocks plus business logic — not a wall of custom CSS.
49- Keep `<style>` blocks small. If a component file has more than ~50 lines of CSS, you probably need a new prop, a new slot, or a new `Ks*` component.
50- Prefer `scoped` styles and rely on design tokens for theming. If you find yourself writing `:deep(.el-...)`, stop — it's a signal the design system needs to expose something.
51- Write each CSS class selector as a full literal — never construct it with SCSS `&` nesting (`&__row`, `&--active`). Constructed selectors can't be found by search and devtools can't jump from a class to its rule. With `scoped` styles, BEM-style namespacing is redundant anyway: use flat, hyphenated names (`.label-input-row`, not `.label-input { &__row }`).
52- Use semantic tokens, not raw colors. `var(--ks-text-link)` communicates intent; `var(--ks-text-blue-500)` does not exist for a reason.
53- Co-locate component-specific tokens (e.g. `--ks-card-shadow`) in the component's SCSS, but always derive them from semantic tokens.
54 
55### When extending the design system
56 
57- Only expose props that are actually used somewhere in the codebase. Speculative props rot.
58- Mirror Element Plus prop names where possible — predictability is a feature.
59- Pass `v-bind="$attrs"` and forward slots so wrappers don't trap consumer extension points.
60- Add the new component or prop to the relevant table in this file, plus a Storybook story and a unit test, in the same PR.
61- Document tokens in code comments next to where they're declared in `ks-theme-*.scss`. The `scripts/generate-palette.mjs` file is auto-generated — don't hand-edit it.
62 
63### When reviewing a UI PR
64 
65Reject (or ask to fix) anything that:
66 
67- Imports from `element-plus` directly into `ui/src/`.
68- Uses a hex code, `rgb(...)`, `--el-*`, or `--bs-*` for color.
69- Uses `:deep()` to reach into a `Ks*` or `el-*` component.
70- Hardcodes pixel values for padding, margin, radii, font sizes, or shadows.
71- Adds a CSS class that overrides `.el-...` selectors.
72- Duplicates a component that already exists in the design system.
73- Adds a `Ks*` component without a Storybook story or test.
74- Mounts `KsDataTable` without binding `:currentPage` / `:pageSize` (or `v-model:currentPage` / `v-model:pageSize`) — pagination is controlled; see "Data tables & pagination state".
75- Watches a `computed` that returns a fresh object (spread / `{...}`) with `{deep: true}` — that fires on every dependency change regardless of content. See "The deep-watch / computed-spread trap".
76- Adds a modal/drawer where the user enters data without guarding accidental dismissal — see "Unsaved input in modals (discard guard)".
77 
78### Accessibility
79 
80- Every icon-only `KsIconButton` must have an accessible label (`aria-label` or `title`). Screen readers do not see the icon glyph.
81- Never convey state with color alone — pair status colors with an icon (`KsExecutionStatus` already does this) or a text label.
82- Use semantic HTML inside slots: real `<button>`, `<a>`, `<label>`, headings in document order. Don't fake interactivity with `<div @click>`.
83- `KsDialog`, `KsDrawer`, `KsPopover` already manage focus trap and `Escape`-to-close — don't reimplement these in feature code.
84- Keep tab order logical; rely on the DOM order rather than `tabindex` hacks.
85- Color contrast comes for free as long as you use `--ks-text-*` against `--ks-background-*` pairings. If you mix-and-match, verify with the browser inspector.
86 
87### Internationalization
88 
89- No hardcoded user-facing strings. Always go through i18n.
90- **In `<template>`, always use the global `$t(...)`** — never the `t` from `useI18n()`. Only call `useI18n()` (`const {t} = useI18n()`) when you need `t` in `<script>` (computed labels, toasts, etc.); if a component needs i18n **only** in its template, use `$t` and don't import `useI18n` at all.
91- Use `<i18n-t>` for plurals and interpolation — never string-concatenate.
92- Format dates and times via `dateUtils` (which respects `TIMEZONE_STORAGE_KEY` and `DATE_FORMAT_STORAGE_KEY`); format durations via `durationUtils.humanDuration()`. Don't reach for `Intl.DateTimeFormat` directly.
93- Strings owned by a `Ks*` component live in the design system's locale files and are registered via `registerDesignSystemI18n`. Strings owned by a feature live in that feature's locale files.
94 
95### Loading, empty, and error states
96 
97Every async surface must render all four states. "Happy path only" is a bug.
98 
99- **Loading:** `KsSkeleton` for content placeholders; `vKsLoading` directive for sections that already have layout; `KsLoading` component for full-page or container-level spinners.
100- **Empty:** `KsEmpty` with an action where possible — never a blank screen.
101- **Error:** `KsAlert type="error"` with retry affordance, or `KsMessage` for transient errors.
102- **Success / data:** the actual content.
103 
104### Data tables & pagination state
105 
106`KsDataTable` is a **fully controlled component** for pagination. `props.currentPage` and `props.pageSize` are the single source of truth — the component holds no internal page mirror. The parent owns the state, binds it (URL or local ref), and the component reacts.
107 
108**The contract:**
109 
110- Bind `:currentPage` / `:pageSize` (one-way) OR use `v-model:currentPage` / `v-model:pageSize` (two-way).
111- Listen to `@page-changed` (or rely on `@update:currentPage`/`@update:pageSize` via v-model) and propagate the change to the bound state — typically a `router.push({...route.query, page: String(page), size: String(size)})`.
112- The component watches `[currentPage, pageSize]` and re-fires `loadData` automatically when either prop changes. Do **not** call `dataTable.reload()` from the parent in response to a page click — the prop change handles it.
113- `resetAndReload()` emits `update:currentPage(1)` and `page-changed`; if the page was already 1 it just reloads. Useful from a filter-change watcher to bounce back to page 1 + re-fetch.
114 
115**URL-driven pattern** — the default for top-level list pages (Logs, Flows, Executions, KV, Secrets, Triggers, FlowsSearch, Blueprints):
116 
117```vue
118<KsDataTable
119 :loadData="loadData"
120 :currentPage="urlPage"
121 :pageSize="urlSize"
122 :total="store.total"
123 @page-changed="({page, size}) => router.push({query: {...route.query, page: String(page), size: String(size)}})"
124/>
125 
126<script setup>
127const urlPage = computed(() => Number(route.query.page) || 1)
128const urlSize = computed(() => Number(route.query.size) || 25)
129</script>
130```
131 
132**Local-state pattern** — for embedded tables that should not appear in the URL (MetricsTable, side-panel views):
133 
134```vue
135<KsDataTable
136 v-model:currentPage="currentPage"
137 v-model:pageSize="pageSize"
138 :loadData="loadData"
139 :total="..."
140/>
141 
142<script setup>
143const currentPage = ref(1)
144const pageSize = ref(25)
145</script>
146```
147 
148**Never** maintain a separate `internalPage` / `pageNumber` ref *and* bind the prop to a different value — that re-introduces the drift bug (URL says page 2, UI shows page 1) that this contract exists to prevent.
149 
150### The deep-watch / computed-spread trap
151 
152A `computed` that returns a fresh object (via spread or `{...}`) returns a new reference on every evaluation. Watching it with `{deep: true}` does **not** add structural equality — `deep: true` enables deep dependency tracking; the equality check at the top is still `Object.is`. The callback therefore fires on every dependency change, even when the content is unchanged.
153 
154This was the root cause of the logs pagination bug: the watcher reset the page to 1 on every `route.query` mutation, including page-only updates from the user clicking the pagination itself.
155 
156**Don't:**
157 
158```ts
159const filterQuery = computed(() => {
160 const {page: _p, size: _s, sort: _so, ...filters} = route.query
161 return filters // new object reference on every route.query change
162})
163watch(filterQuery, () => dataTable.value?.resetAndReload(), {deep: true})
164// Fires on every route.query change — page clicks, sort clicks, anything —
165// and bounces the user back to page 1.
166```
167 
168**Do:**
169 
170```ts
171const filterQueryKey = computed(() => {
172 const {page: _p, size: _s, sort: _so, ...filters} = route.query
173 return JSON.stringify(filters) // stable string — same content, same value
174})
175watch(filterQueryKey, () => dataTable.value?.resetAndReload())
176// Fires only when filter content actually changes.
177```
178 
179The general rule: **if you find yourself reaching for `{deep: true}` on a computed source, the source should probably return a primitive (string / number) instead of an object.** Strings compare by value; references compare by identity. Picking the right primitive is the fix.
180 
181### Unsaved input in modals (discard guard)
182 
183Any modal/drawer where the user **enters data** must not silently lose it on an accidental dismissal. Use the shared `useDiscardGuard` composable — never reimplement the confirm-before-discard logic per modal.
184 
185```ts
186// ui/src/composables/useDiscardGuard.ts (import path is relative to your component)
187import {useDiscardGuard} from "../../composables/useDiscardGuard"
188 
189// isDirty: true when there is unsaved input worth a prompt
190const {guardedClose} = useDiscardGuard(() => /* isDirty */, {message: t("...")}) // message optional; defaults to "discard changes confirmation"
191const beforeClose = (done: () => void) => guardedClose(() => { reset(); done() })
192```
193 
194```vue
195<KsDialog :beforeClose="beforeClose" ... />
196<KsDrawer :beforeClose="beforeClose" ... />
197```
198 
199Rules:
200- **Guard only *accidental* close paths** — overlay click, `Escape`, the `X`. These all go through `beforeClose`. Explicit **Cancel / Save** buttons set `v-model = false` directly and **must not** be guarded (the user already expressed intent; a prompt there is friction). Note: a programmatic `v-model = false` does **not** trigger `beforeClose` (Element Plus only calls it for user-initiated closes), which is exactly why Cancel/Save bypass it.
201- **`isDirty` is per-modal.** Compare current input against a baseline captured on open (`JSON.stringify` snapshot), or "any meaningful input"; **ignore empty rows** (e.g. a blank label/tag row is not dirty). Reset dirty-relevant state on open so a reopen starts clean.
202- **`KsDialog` and `KsDrawer` both expose a `beforeClose` prop** with signature `(done) => void` — call `done()` to proceed with closing. (Element Plus's `ElDrawer.beforeClose` is a prop, not an event; `KsDrawer` forwards it.)
203- **Don't guard** read-only viewers, action/confirmation dialogs, or ephemeral forms that reset on every open.
204 
205### Icons
206 
207- All icons come from [`vue-material-design-icons`](https://github.com/robcresswell/vue-material-design-icons) via `<KsIcon>` (or `<KsIconButton>` for clickable icons).
208- Never inline raw SVG, font-icon classes, or emoji as UI state. If a needed icon is missing, propose adding it to the DS rather than dropping an SVG into a feature folder.
209- Pass `name` (the kebab-case Material name); size and color come from props or the surrounding token context — don't override with inline `style`.
210 
211### Performance
212 
213- Lazy-load heavy surfaces: `KsEchart`, `KsLine`, `KsBar`, `KsPie`, `KsGraph`, `KsMarkdown`, code-editor surfaces. Use `defineAsyncComponent` or route-level code splitting.
214- Prefer `v-show` for frequent toggles (tabs, filters), `v-if` for rare/heavy mounts (modals, big tables).
215- Pass stable `key` props in lists. Avoid index-based keys when items have IDs.
216- Don't render giant tables without `KsDataTable`'s pagination/virtualization — server-side paging is the default for anything that can grow.
217- Watch out for `watch(..., { deep: true })` and `computed` with object identity — they often re-run more than you expect.
218 
219### Testing UI
220 
221- Unit tests with **Vitest** + `@vue/test-utils`, colocated next to the component.
222- Use `data-test="..."` selectors for E2E tests with **Playwright**. Never select on `.el-*` or `.ks-*` class names — those are not stable contracts and will break on Element Plus / DS upgrades.
223- Storybook stories cover: each variant prop, dark mode, edge cases (empty content, very long text, error state). A `*.stories.ts` file with one default story is not enough.
224- Visual regressions caught in Storybook are cheaper to fix than caught in production.
225 
226### Deprecation contract
227 
228When retiring a `Ks*` component, prop, or token:
229 
2301. Mark with a `@deprecated` JSDoc tag *and* a one-line replacement path: `@deprecated since 0.x — use <KsNewThing> instead`.
2312. Keep it working for at least one minor release; add a `console.warn` in dev mode if the cost is reasonable.
2323. Migrate all callers in the same release where feasible — don't leave half-migrations.
2334. Only delete after the deprecation window. A silent removal breaks downstream EE / plugin code.
234 
235## Anti-patterns (do not write these)
236 
237```vue
238<!-- Wrong: raw element-plus, hex color, :deep, SCSS variable in feature code -->
239<template>
240 <el-button class="my-btn">Save</el-button>
241</template>
242<style lang="scss" scoped>
243 .my-btn {
244 background: #8405ff;
245 font-size: $font-size-md;
246 }
247 :deep(.el-button__text) { color: white; }
248</style>
249```
250 
251```vue
252<!-- Right: Ks component, semantic tokens, no deep selector, i18n -->
253<template>
254 <KsButton type="primary">{{ t("save") }}</KsButton>
255</template>
256<style lang="scss" scoped>
257 /* Almost always: no custom CSS is needed at all. */
258</style>
259```
260 
261If your `<style>` block needs to exist:
262 
263```scss
264/* Right: --ks-* tokens, no SCSS vars in feature code, no :deep */
265.my-feature {
266 background: var(--ks-bg-surface);
267 color: var(--ks-text-primary);
268 border: 1px solid var(--ks-border-primary);
269}
270```
271 
272## Components
273 
274### Basic / Layout
275 
276| Component | Purpose |
277|-----------|---------|
278| `KsButton` / `KsButtonGroup` | Primary action button and grouped buttons |
279| `KsIcon` / `KsIconButton` | Material Design icon display; icon-only button (always with `aria-label`) |
280| `KsLink` | Styled hyperlink |
281| `KsText` | Typography wrapper — preferred over raw `<span>` / `<p>` for theme-aware text |
282| `KsScrollbar` | Custom-styled scrollbar wrapper |
283| `KsContainer` / `KsHeader` / `KsMain` | Page layout shell |
284| `KsRow` / `KsCol` | Responsive grid |
285| `KsSplitter` / `KsSplitterPanel` | Resizable split-pane layout |
286 
287### Feedback
288 
289| Component | Purpose |
290|-----------|---------|
291| `KsAlert` | Alert banner for messages and status feedback |
292| `KsDialog` | Modal dialog (handles focus trap + Escape) |
293| `KsDrawer` | Side drawer / panel |
294| `KsTooltip` | Hover tooltip |
295| `KsPopover` | Popover for contextual content |
296| `KsLoading` (`vKsLoading`) | Loading spinner directive |
297| `KsMessage` | Toast notification service |
298| `KsNotification` | Notification service |
299| `KsMessageBox` | Confirmation dialog service |
300 
301### Form
302 
303| Component | Purpose |
304|-----------|---------|
305| `KsInput` / `KsPassword` | Text and password inputs |
306| `KsInputNumber` | Numeric input with increment / decrement |
307| `KsSelect` / `KsOption` / `KsOptionGroup` | Dropdown select |
308| `KsAutocomplete` | Autocomplete input with suggestions |
309| `KsCheckbox` / `KsCheckboxGroup` / `KsCheckboxButton` | Checkbox variants |
310| `KsRadio` / `KsRadioGroup` / `KsRadioButton` | Radio button variants |
311| `KsSwitch` | Toggle switch |
312| `KsDatePicker` / `KsTimePicker` | Date and time pickers |
313| `KsColorPicker` | Color picker |
314| `KsDurationPicker` | ISO 8601 duration picker |
315| `KsCascaderPanel` | Cascading hierarchical selector |
316| `KsUpload` | File upload |
317| `KsForm` / `KsFormItem` | Form container with validation |
318 
319### Data Display
320 
321| Component | Purpose |
322|-----------|---------|
323| `KsCard` | Card container |
324| `KsTable` / `KsTableColumn` | Basic table |
325| `KsDataTable` / `KsFilter` / `KsBulkSelect` | Advanced data table with filtering, sorting, pagination, bulk actions. **Pagination is fully controlled** — bind `:currentPage` / `:pageSize` (or `v-model:`). See "Data tables & pagination state". |
326| `KsEntityLink` | Clickable cross-entity reference (namespace / flow) for table cells — neutral tag with leading icon, violet on hover |
327| `KsBadge` | Small indicator badge |
328| `KsNewBadge` | Compact uppercase "NEW" pill flagging a newly shipped feature — caller supplies the label via the default slot |
329| `KsTag` / `KsCheckTag` | Tag / label; clickable checkbox-style tag |
330| `KsAvatar` | Avatar with fallback |
331| `KsProgress` | Progress bar |
332| `KsPagination` | Pagination controls |
333| `KsEmpty` | Empty state placeholder |
334| `KsSkeleton` | Skeleton loader |
335| `KsId` | Copyable ID display |
336| `KsDateAgo` | Relative time display ("2 hours ago") |
337| `KsSegmented` | Segmented control |
338| `KsCollapse` / `KsCollapseItem` | Collapsible sections |
339| `KsTree` | Hierarchical tree view |
340| `KsTimeline` / `KsTimelineItem` | Timeline visualization |
341| `KsExecutionStatus` | Execution / task status badge with icon and color |
342| `KsCodeStatus` | Compact validity badge with icon (`valid` / `error`) — caller supplies the label |
343| `KsMarkdown` | Markdown renderer (lazy-load on heavy surfaces) |
344 
345### Charts
346 
347| Component | Purpose |
348|-----------|---------|
349| `KsEchart` | ECharts base wrapper (lazy-load) |
350| `KsLine` / `KsBar` / `KsPie` | Line, bar, and pie charts (lazy-load) |
351| `KsGraph` | Graph / network visualization (lazy-load) |
352 
353### Navigation
354 
355| Component | Purpose |
356|-----------|---------|
357| `KsTabs` / `KsTabPane` | Tabbed interface |
358| `KsMenu` / `KsMenuItem` | Hierarchical menu |
359| `KsDropdown` / `KsDropdownMenu` / `KsDropdownItem` | Dropdown menu |
360| `KsTopNavBar` | Top navigation bar |
361| `KsSideBar` / `KsSideBarSection` / `KsSideBarItem` | Left sidebar shell (header / scrollable body / footer slots), section with title, and styled link primitive with icon, active and locked states |
362| `KsBreadcrumb` / `KsBreadcrumbItem` | Breadcrumb navigation |
363| `KsSteps` / `KsStep` | Step / wizard progress indicator |
364 
365## Utilities (import from the design system)
366 
367- `State`, `STATES`, `LOG_LEVELS` — execution state constants, icons, and colors
368- `cssVar(name, opacity?)` — read a `--ks-*` CSS custom property at runtime (use this in JS / chart configs instead of hardcoding hex)
369- `dateUtils` — `dateFilter()`, `DATE_FORMAT_STORAGE_KEY`, `TIMEZONE_STORAGE_KEY`
370- `durationUtils` — `duration()`, `humanDuration()` — ISO 8601 ↔ ms and human-readable
371- `stringUtils` — `afterLastDot()`
372- `flowYamlUtils` — YAML parsing / manipulation for flow definitions
373- `Comparators` — enum of filter comparison operators
374- Filter helpers — `decodeSearchParams()`, `encodeFiltersToQuery()`, `getUniqueFilters()`, etc.
375- `applyDefaultFilters()`, `useRouteFilterPolicy()` — filter composables
376- `setMomentInstance()`, `setDateFormatter()` — date library configuration
377- `designSystemLocale`, `setDesignSystemLocale`, `registerDesignSystemI18n` — i18n
378 
379## Composables
380 
381- `useTheme()` — detects and tracks dark / light mode via MutationObserver. Use this instead of reading `document.documentElement` yourself.
382- `useFilters`, `useSavedFilters`, `useDefaultFilter`, `usePreAppliedFilters`, `useRouteFilterPolicy`, `useTableColumns`, `useDataOptions`, `useDragAndDrop`, `usePeriodicRefresh` — data-table filter composables
383- `useDiscardGuard(isDirty, {message?})` — confirm-before-discard for data-entry modals; see "Unsaved input in modals (discard guard)"
384- `useTaskIcon()` — resolves the app-provided task-icon component via `TASK_ICON_INJECTION_KEY` (falling back to a generic placeholder icon). The app provides its own `TaskIcon` component once, at bootstrap (`app.provide(TASK_ICON_INJECTION_KEY, TaskIcon)`) — the design system cannot own that component since it depends on the app's plugin-icon backend API. Used internally by `KsEditor` (Monaco suggestion icons) and the `@kestra-io/topology` package (graph node icons) so both share the same app-provided instance.
385 
386## Design tokens
387 
388Tokens are CSS custom properties declared in [`ks-theme-light.scss`](packages/design-system/src/assets/styles/ks-theme-light.scss), [`ks-theme-dark.scss`](packages/design-system/src/assets/styles/ks-theme-dark.scss) and [`ks-theme-dark-2.scss`](packages/design-system/src/assets/styles/ks-theme-dark-2.scss). Each token is **semantic** — it describes *what the value means*, not what color it is. That is what makes dark mode and rebrands trivial.
389 
390**Always use `var(--ks-*)` in component `<style>` blocks** — not SCSS variables, not hex codes, not `--el-*`, not `--bs-*`.
391 
392Token families currently exposed:
393 
394- `--ks-bg-*` — backgrounds: surfaces (`base`, `surface`, `elevated`, `sidebar`, `input`, `overlay`, `scrim`), interaction states (`hover`, `hover-elevated`, `active`, `inactive`), component fills (`badge`, `tag`, `tag-hover`, `tag-active`, `tag-inactive`), plus per-state (`--ks-bg-success`, `--ks-bg-error`, `--ks-bg-warning`, `--ks-bg-info`)
395- `--ks-border-*` — `default` / `subtle` / `strong` borders, `focus`, plus per-state (`error`, `success`, `warning`, `info`)
396- `--ks-text-*` — text colors: `primary`, `secondary`, `dim`, `muted`, `inactive`, `link`, named (`blue`, `green`), plus per-state (`error`, `success`, `warning`, `info`)
397- `--ks-icon-*` — icon colors: `default`, `hover`, `active`, `inactive`, `muted`, plus per-state
398- `--ks-btn-*` — button background / border / text variants (`primary`, `secondary`, `run`, `success`) across `default` / `hover` / `active` / `inactive` states
399- `--ks-toggle-*` — toggle / switch states (`default`, `hover`, `active`, `inactive`, `playground`)
400- `--ks-dropdown-*`, `--ks-scrollbar-*`, `--ks-shadow-*` — component-specific tokens
401- `--ks-status-*` — palette for charts and status (`success`, `error`, `warning`, `info`, `running`, `pending`, `neutral`); pair with `cssVar("--ks-status-success")` in JS
402- `--ks-editor-*`, `--ks-dependencies-*`, `--ks-topology-*` — domain-specific surfaces
403 
404When a needed token is missing, **add it** to all three of `ks-theme-light.scss`, `ks-theme-dark.scss` and `ks-theme-dark-2.scss` (and review with design) rather than picking a raw color.
405 
406**SCSS variables — only inside `ui/packages/design-system/`, never in feature code:**
407 
408- **Brand:** `$base-primary-500` (primary, `#8405FF`)
409- **Status palette:** `$base-green-500` (success), `$base-red-500` (danger), `$base-orange-500` (warning), `$base-blue-500` (info)
410- **Grays:** `$base-gray-50` … `$base-gray-950`
411- **Typography:** `$font-family-sans-serif` (Inter), `$font-family-monospace` (JetBrains Mono)
412- **Font sizes:** `$font-size-xs` / `sm` / `md` / `lg` / `xl` / `2xl` / `3xl` / `4xl`
413- **Radii:** `$border-radius` (0.25rem), `$border-radius-sm` (0.15rem), `$border-radius-lg` (0.5rem)
414 
415These exist so the *design system itself* can compose tokens from a single palette. They are not API for feature code — feature code should reach the same values through `--ks-*` tokens.
416 

Sections

  • UI Design System Guidelines
  • What this is, in plain terms
  • Golden rules (non-negotiable)
  • Best practices for keeping the design system healthy
  • Before you write code
  • While you write code
  • When extending the design system
  • When reviewing a UI PR
  • Accessibility
  • Internationalization
  • Loading, empty, and error states
  • Data tables & pagination state
  • The deep-watch / computed-spread trap
  • Unsaved input in modals (discard guard)
  • Icons
  • Performance
  • Testing UI
  • Deprecation contract
  • Anti-patterns (do not write these)
  • Components
  • Basic / Layout
  • Feedback
  • Form
  • Data Display
  • Charts
  • Navigation
  • Utilities (import from the design system)
  • Composables
  • Design tokens

What it covers

testcode-stylearchitecturegit-prapiuiperformancedo-notdocs

Stack — with the evidence

typescript

(1.00)

java

(1.00)

node

(1.00)

vite

(1.00)

vitest

(1.00)

eslint

(1.00)

docker

(1.00)

playwright

(0.95)

react

(0.70)

vue

(0.70)

javascript

(0.60)

kubernetes

(0.60)

github-actions

(0.60)

Format

AGENTS.md

A plain-markdown README for coding agents, deliberately unopinionated: no frontmatter, no globs, no vendor keys. That minimalism is why it became the one file a dozen different agents will read, and why it carries the least per-file targeting power of any format here.

What the corpus says about it

Repository

Owner
kestra-io
Language
—
License
—
Archived
no

All configs in this repo

Also in kestra-io/kestra

Diff this repo’s formats

One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?

The other instruction files in this repository
RepositoryFormatStackCoversScoreChanged
kestra-io/kestraAGENTS.md · 28kAGENTS.mdjavanode+11setupbuildteststyle+981/100today
Diff against AGENTS.md

Similar configs

Same format, overlapping stack, ranked by quality.

Same format, overlapping stack, ranked by quality
RepositoryFormatStackCoversScoreChanged
n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199kAGENTS.mdtypescriptlangchain+16buildteststylearch+3100/1003 days ago
TryGhost/Ghoste2e/AGENTS.md · 55kAGENTS.mdtypescriptjavascript+12setupteststylearch+2100/1003 days ago
duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70AGENTS.mdtypescriptjavascript+5buildteststylearch+3100/1003 days ago
mui/material-uiAGENTS.md · 99kAGENTS.mdtypescriptjavascript+13setupbuildtestlint-format+9100/1003 days ago
aaif-goose/gooseAGENTS.md · 52kAGENTS.mdrusttypescript+2setupbuildtestlint-format+6100/1003 days ago
SkeneTechnologies/skene-cookbookAGENTS.md · 51AGENTS.mdpythoneslint+4setupbuildtestlint-format+7100/1002 days ago
trick77/agents-md-syncAGENTS.md · 2AGENTS.mdtypescriptnode+4setupbuildteststyle+5100/1003 days ago
code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67kAGENTS.mdtypescriptbun+10setupbuildtestlint-format+6100/1002 days ago
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack