Skip to content

Releases: Kanzo-Tech/ui

v0.49.0

Choose a tag to compare

@angel-ip angel-ip released this 20 Oct 09:49
664e599
Merge pull request #248 from Kanzo-Tech/claude/remove-duckdb-sts-log-…

v0.48.0

Choose a tag to compare

@angel-ip angel-ip released this 15 Oct 22:12
9992003
feat(auth)!: @kanzo-tech/auth reads the person from the platform edge…

v0.47.0

Choose a tag to compare

@angel-ip angel-ip released this 15 Oct 13:08
d72e388
Update release.yml

v0.46.0

Choose a tag to compare

@angel-ip angel-ip released this 14 Oct 13:22
a4084c2

@kanzo-tech/ui (minor)

  • Mosaic has one door: @kanzo-tech/mosaic. @kanzo-tech/ui/analytics now exports only the charts and their parts.
    • Breaking: import engine, Engine, Coordinator, Selection, JoinGraph, Relation, RelationExpr, relationKey and the other relation* helpers, numbers, queryFailure, Query, TableExpr, ExprValue, FilterExpr, MosaicClient, makeClient, every clause*, onRelation, the load* loaders, the aggregates, sql, asc and desc from @kanzo-tech/mosaic. Split the import in two:

      import { count, engine, Query, type JoinGraph } from "@kanzo-tech/mosaic";
      import { ChartBarY, ChartRoot, Dashboard, MosaicProvider } from "@kanzo-tech/ui/analytics";

      bin and the six axis marks (axisX, axisY, axisFx, axisFy, gridFx, gridFy) stay on /analytics. @kanzo-tech/ui no longer lists @uwdata/mosaic-core and @uwdata/mosaic-sql as its own peers: they are @kanzo-tech/mosaic's, so keep them installed beside it.

  • A relation's fields are read once per page, on your own TanStack Query client. useFieldStats (and with it Dashboard and AnswerCard) reads a relation's SUMMARIZE once for every part that asks for it, never retries, and reads again only for a new relation or after a failure. The reads show in your client's devtools.
    • Breaking: install @tanstack/react-query 5 and render one QueryClientProvider above a page that reads fields through @kanzo-tech/ui/analytics (Dashboard, useFieldStats). Without it those hooks throw TanStack's No QueryClient set. The charts on their own need neither. queryFieldStats is gone: use useFieldStats, or run SUMMARIZE yourself and pass the rows to fieldStats. Clear your client on sign-out.
  • Breaking: valibot is an optional peer of @kanzo-tech/ui/analytics rather than a dependency of @kanzo-tech/ui. Install it if you use /analytics; an app that never imports /analytics no longer installs it.
  • Every component takes the words it draws through one prop, translations. The Preferences parts take theirs once on PreferencesRoot, and a part's own translations win for that part. A translated panel no longer has English left in it.
    • Breaking: pass translations, not copy, to PreferencesPanel, PreferencesSections and Pref. The type is PreferencesTranslations (was PreferencesCopy). PreferencesPanel's title and hint are entries of its translations. The retired-theme notice's words are retiredTitle and retired(theme) (were retired: { title, formatDescription }).
    • Breaking: pass translations, not labels, to FindingsRoot, FindingsItem, FindingsGroup and FindingsTally. The type is FindingsTranslations (was FindingLabels).
    • Breaking: MadeWith's prefix, infix and loveLabel are entries of its translations: <MadeWith translations={{ prefix: "Hecho con", infix: "en", loveLabel: "amor" }}>.
  • Problem takes the host's words for a code through describe(code, data), which returns a ProblemDescription ({ title?, detail?, link?, page? }, the same fields as before). translations stays for the words Problem draws itself.
    • Breaking: rename copy= to describe= on Problem, and the type ProblemCopy to ProblemDescription. The function you pass is unchanged.
  • RelationPicker inflects no noun. An in-edge reads as the type it brings in and the edge: Comment · located in this place, where it read Comments located in this place. Every word it draws is an entry of translations (RelationPickerTranslations, from @kanzo-tech/ui/analytics), including the two sentence patterns out(to, label) and in(source, label, destination).
    • Breaking: plural is gone. A sentence that reads wrong is phrase(edge, direction)'s to reword, or translations.in's. A test that found a hop by its sentence reads the new one: RelationPickerHarness.add("Comment · located in this place").
  • One facet item. DataTableFacetFilter and ChartFilter take items of FacetFilterItem ({ value, label?, icon?, count? }, from @kanzo-tech/ui), as FacetFilter already did. DataTableFacetFilter keeps an item's own count and gives an item without one the column's count.
    • Breaking: pass items, not options. DataTableFacetOption and ChartFilterOption are gone; type your items as FacetFilterItem. A ChartFilter item is an object with a string value ({ value: "3" }, not a bare 3).
  • Breaking: ChartPickX, ChartPickY, ChartPickColor, ChartBrushX, ChartBrushY and ChartBrushXY are gone from /analytics. Write ChartToggleX, ChartToggleY, ChartToggleColor, ChartIntervalX, ChartIntervalY and ChartIntervalXY; they were the same components under a second name.
  • Breaking: @kanzo-tech/ui/table no longer re-exports TanStack's Table type, because Table from @kanzo-tech/ui is the markup component. Write import type { Table } from "@tanstack/react-table" where you name the instance useDataTable returns.
  • Preferences, ThemeNotice and FloatingPanel leave the package; each is now an example in the docs to copy.
    • Breaking: replace <Preferences hotkey="p" triggerClassName="…" /> with <PreferencesRoot hotkey="p"><PreferencesTrigger className="…" /><PreferencesPanel /></PreferencesRoot>. PreferencesProps is gone.
    • Breaking: ThemeNotice, ThemeNoticeProps and ThemeRetiredCopy are gone. Read useKanzoTheme().retiredTheme and toast it once, as /docs/hooks/use-kanzo-theme#the-retired-theme-as-a-toast does.
    • Breaking: FloatingPanel, FloatingPanelResizeHandle and their props are gone. Copy /docs/layout/floating-panel's example.
  • Six names no product called leave the package:
    • KanzoTheme and KanzoThemeProps. A region that wears another theme is data-theme="<theme>" and the side's class on an element of yours (/docs/themes#theming-a-region). KanzoThemeProvider stays the one themer.
    • KanzoThemeName, which was string. Write string.
    • safeParseColor. ColorPicker still takes a blank or half-typed value; for a parse of your own, wrap parseColor in a try.
    • useFindings. Read the findings where you passed them to FindingsRoot.
    • From /analytics, parseDashboard and recommend (with Recommendation and RecommendIntent). Read one relation's spec with parseDashboards({ byRelation: { [relation]: saved } }); autoDashboard is the recommender's door.
  • A question is one part: Question, with QuestionPrompt, QuestionDescription, QuestionChoices, QuestionChoice, QuestionInput, QuestionTextarea and QuestionError. It works on its own, as a guided chat draws a decision in a turn, and inside a Questionnaire, which is now only the sequence around its questions: the root, QuestionnaireProgress and the actions. On its own a question holds its answer as value, defaultValue and onValueChange({ value }), and a pick answers it in one press; inside a Questionnaire the answer is the questionnaire's, under the question's name. name is also the form field's name, so a question in a plain <form> submits its answer.
    • Breaking: inside a Questionnaire, QuestionnaireItem is Question, QuestionnaireTitle is QuestionPrompt, QuestionnaireDescription is QuestionDescription, QuestionnaireChoices is QuestionChoices, QuestionnaireChoice is QuestionChoice, QuestionnaireInput is QuestionInput, QuestionnaireTextarea is QuestionTextarea and QuestionnaireError is QuestionError, with the same props. useQuestionnaireItem is useQuestion, which also carries answer and setAnswer. The types follow: QuestionnaireItemProps, QuestionnaireChoicesProps and QuestionnaireChoiceProps are QuestionProps, QuestionChoicesProps and QuestionChoiceProps, and QuestionnaireAnswer is QuestionAnswer.
    • Breaking: useQuestionnaire() carries answers, questions, steps and translations; its blocked, indexOf, setAnswer and shortcuts are gone. Answer from inside a question with useQuestion().setAnswer.
    • Breaking: the parts' data-slots follow their names: questionnaire-item is question, questionnaire-title is question-prompt, and every other questionnaire-… slot of a question's part is question-….
  • Questionnaire and Question take their words through translations. Questionnaire's are progress(position, count), navigation (the actions row's accessible name), previous, skip, next, submit, and its questions' unanswered, which reach every question inside it; a Question's own translations win for that question. An action given children still says those.
    • Breaking: QuestionnaireProgress takes no children. Translate it with <Questionnaire translations={{ progress: (position, count) => `Pregunta ${position} de ${count}` }}>, which aria-valuetext reads too.
  • A dashboard tile carries its own question: where, conditions under the page's filter ({ field, in }, { field, between } or { field, contains }, the Condition type; a field is a path from the relation's root, crossed in when it leaves the relation), and top, a bar chart's first bars by its measure or a table's first rows. tileQuery(table, tile, context) is the one compiler of a tile, which every tile view reads through and @kanzo-tech/ai/data's answers read too; TileView draws a tile of any kind, with actions in its header. TILE_ROWS, TileContext, TileQuery, Condition, ConditionValue and TileViewProps are exported from @kanzo-tech/ui/analytics. A tile naming a field or path its relation cannot read draws what it lacks.
    • Breaking: measureExpr and bucketExpr are no longer exported: read a tile's measure and rows through tileQuery. A host that reads til...
Read more

v0.45.1

Choose a tag to compare

@angel-ip angel-ip released this 13 Oct 19:50
f1c00cb

@kanzo-tech/mosaic — patch

The engine reads JSON. engine() now loads DuckDB's json extension at boot, beside parquet and httpfs, shipped in the package and hash-pinned like them. read_json_auto now works on the engine: a profile of a .json file, a query that names one, and loadJSON from @kanzo-tech/mosaic or @kanzo-tech/ui/analytics. Before, each of these failed with "Table Function with name read_json_auto is not in the catalog, but it exists in the json extension". You do not need to change anything. Your bundler emits json.duckdb_extension.<hash>.wasm as one more asset from your own origin, autoloading stays off, and the page's content security policy is the same.

@kanzo-tech/ui — patch

Filters stay when the dashboard changes relation (#203). A pick on a chart is the page's filter, not the chart's. When a Dashboard switches to another relation (from a RelationPicker, an Ask answer's Open on the dashboard, or a key change), the pick stays in the FilterBar as a chip and keeps filtering the new relation by key. It goes away only when you remove it: its chip, a re-pick or clear on the chart, Clear every filter, or taking the filter off the dashboard. Switch back and the chart shows the pick it still holds. ChartFilter, ChartSearch, ChartSlider and ChartTimeline behave the same way when they unmount and mount again. If your page relied on unmounting a chart to clear its filter, call selection.reset() (from useMosaic()) where you unmount it.

v0.45.0

Choose a tag to compare

@angel-ip angel-ip released this 13 Oct 13:45
083f41f

@kanzo-tech/llm (minor)

  • A local model's load is something you read. localModelLoad(id) from @kanzo-tech/llm/local answers where the download and load stand: { state: "idle" }, { state: "loading", progress, text }, { state: "ready" } or { state: "failed", error }. The type is ModelLoad, on @kanzo-tech/llm. It answers the same whichever screen started the load. watchLocalModel(id, listener) says when it changes, so useSyncExternalStore((l) => watchLocalModel(id, l), () => localModelLoad(id)) is the whole subscription.
  • Breaking: LocalModelSettings.onProgress is gone. A host that kept its own store from it reads localModelLoad instead, and draws it with @kanzo-tech/ai's ModelBadge.

@kanzo-tech/ai (minor)

  • The parts a guided chat is built from, beside the transcript and the composer:

    • Question, QuestionPrompt, QuestionDescription, QuestionOptions and QuestionOption put a question to the person in a panel. The options are outline buttons and the chosen one is filled. It is a radio group, so the arrow keys move between options, and onValueChange is the answer, in one press. A Task's Decision has this shape.
    • Working says work is under way: a spinner and its words in a role="status", with @kanzo-tech/ui's Progress beside it when there is a measure.
    • ModelBadge (ModelBadgeProps, ModelBadgeTranslations) shows the model in the page as a badge: Preparing the model · 42%, Model ready or Model failed. Pressing it opens the progress bar and the engine's words, or the failure as a Problem with a retry (no retry for ai/unavailable). Give it localModelLoad's value.

    These replace a decision drawn with RadioGroup, a spinner beside a sentence, or a model-loading bar of your own.

  • ConversationScrollButton no longer stays on screen with nothing to scroll to. When the transcript stops overflowing without a scroll (the panel grew, the device turned, a turn was removed), Conversation measures again and takes the pin back.

  • Chat and Assist draw a model's offers with @kanzo-tech/ui's Suggestions.

@kanzo-tech/ui (minor)

  • Suggestions takes pending: that many pills in skeleton after the ones already there, and the strip stays aria-busy until they land. It is for offers that arrive one by one, such as a model's. A strip that drew its own skeletons beside its pills can pass the count instead.

@kanzo-tech/theme (patch)

  • kanzo-dark carries its brand at the same weight as kanzo:

    • --primary and --ring are #5cb572 rather than the neon #1ae973.
    • The first chart series, --chart-1, is the brand's green #52b770 rather than the lime #64c600.
    • The neon stays as --success, as in the light theme.

    Buttons, focus rings and the first series of every chart are calmer on a dark Kanzo page. A host that matched the old neon in its own CSS should read var(--primary) instead.

v0.44.0

Choose a tag to compare

@angel-ip angel-ip released this 13 Oct 09:55
eff15b7

@kanzo-tech/llm (minor)

  • The model runs in the page, from where you serve it. localModel(id, { from: { weights, library } }) loads the model from where you serve it — its MLC weights directory and its compiled .wasm — rather than from WebLLM's prebuilt list on Hugging Face. An id that is in that list keeps the list's settings for it — its overrides (the context window among them), vram_required_MB, low_resource_required, required_features — so a model you serve runs as the prebuilt one would and needs no more memory. loadLocalModel(id, settings) downloads and loads it before the first call, on the same engine, and rejects as a call would. The first load asks the browser to persist the page's storage, so the weights are not evicted under pressure.
  • Breaking: the gateway is gone. createGateway, Gateway and GatewaySettings are removed, and with them the dependency on @ai-sdk/openai-compatible. The model is localModel, from @kanzo-tech/llm/local, in the page; any other LanguageModel still works everywhere one is taken. AiError has one code left, ai/unavailable (data.reason); ai/silent and ai/rate-limited, and the after, retryAfter and status data, were the gateway's and went with it. A host that branched on them drops those branches.

@kanzo-tech/ai (minor)

  • The transcript and the composer are exported, for a conversation whose turns are yours: Conversation, ConversationContent and ConversationScrollButton — the scroll region that follows new content only while the reader is at the end, and the button that takes them back — and PromptInput, PromptInputTextarea, PromptInputToolbar and PromptInputSubmit (PromptInputSubmitProps), the composer where Enter sends, Shift+Enter is a newline and the button is Send, Stop or Retry by the chat's status. A transcript that scrolls itself to the bottom on every turn, or a composer built from InputGroup, can be replaced with them.
  • Doc comments follow the gateway's removal (no API change).

@kanzo-tech/auth (minor)

  • Breaking: the AI gateway is gone from the platform's services. services/compose.yaml and services/compose.dev.yaml include identity only: services/ai (agentgateway, its Postgres, the local-model profile, AI_CHAT, AI_COMPLETE) no longer exists, and the model runs in the page with localModel from @kanzo-tech/llm/local. The realm no longer registers the ai-gateway API, and the realm-wide apis variable and output are gone with it: an application's declaration lists no apis, only its own api. Remove apis: [ai-gateway] from your x-application, and the /api/ai mount from kanzoAuth's apis.

@kanzo-tech/ui (patch)

  • TreeView no longer paints its first row as focused when nothing is: a row's focus background shows for keyboard focus only (:focus-visible), not on the tab stop Ark marks at rest. A className that restyled the focused row through data-focus: should use focus-visible:.

v0.43.0

Choose a tag to compare

@angel-ip angel-ip released this 12 Oct 15:34
6b9aa17

v0.43.0

Ask is a Task: the model fills a small spec, the page finds the relation, and oracles check it before anything runs. Models can run in the page. Breaking: see What to change.

Highlights

  • Tasks (@kanzo-tech/ai/task). defineTask declares what a model may answer and which oracles check it. runTask asks for it, sends the oracles' diagnostics back for a repair, and answers a Proposal: accepted, with its diagnostics, or with a Decision for the person. propose asks for several at once and keeps those the oracles accept. verify checks a value no model wrote. A Task can declare a grammar instead of an output schema when its artefact is text in a language. useTask is a screen's list of proposals. See /docs/ai/tasks.
  • Ask runs in the chat as a Task. useAskChat({ model, engine, graph, types, selection }): each question is one run of ask, its answer is the same answer part AnswerCard draws, and the reply is written from the rows, not by the model. When two relations answer a question equally, the chat asks which, numbered. askOffers gives the questions to start from, each one checked.
  • Chips on an answer. AskChips draws what an answer was read as: what is counted, what a chart is by, each condition. Each chip opens the values it can hold instead, checked by Ask's oracles. Taking one (askEdit) re-reads the answer with no model call.
  • A page chip reaches an answer as it reaches a tile. A chip published on another relation narrows the answer through the key both reach.
  • resolveRelation (@kanzo-tech/mosaic) computes the relation a question needs: the shortest path that covers its fields and reaches the page's chips. It answers that relation, the tied ones (ambiguous), or what nothing reaches (unreachable).
  • A model in the page (@kanzo-tech/llm/local). localModel("Qwen3-4B-q4f16_1-MLC", { worker, onProgress }) runs on WebGPU through WebLLM, an optional peer only this subpath reaches. Without WebGPU a call fails as ai/unavailable. A call can hold the model to a grammar with providerOptions: { kanzo: { grammar } }.

What to change

  • useAgentChat(dataAgent(…)) → useAskChat({ model, engine, graph, types, selection }). types is each type alone: readAnswerRelations(coordinator, graph, answerRelationsOf(graph, { hops: 0 })).
  • dataSuggestions → askOffers (a promise, not a stream).
  • dataAgent, dataInstructions, DataAgentOptions, DataSuggestionsOptions, DataTools and the context: { tokens, reserve } option are gone.
  • Chat's suggestions/suggesting → proposals/proposing. ChatSkeleton's suggestions → proposals.
  • suggest() and SuggestOptions are gone. Questions to start from are a Task's, through propose.
  • AiError's ai/context code, and its tokens and budget data, are gone from @kanzo-tech/llm.

Every @kanzo-tech/* is published at 0.43.0; move them together (they pin each other exactly).

v0.42.0

Choose a tag to compare

@angel-ip angel-ip released this 11 Oct 22:22
fc10119

v0.42.0

One count on the page, a graph grid you can see, and an Ask that waits where its answer will be. Breaking: see What to change.

Highlights

  • One count on the page. What the filters keep was drawn up to four times on one screen (the toolbar, the legend, the footer and each lasso or search chip). It is drawn once now, by the FilterBar readout, at the start of the filter row: 611 of 1,528 people. This is how dc.js's dataCount and Kibana Discover's hit count do it.
    • GraphCounts says what is drawn: 5K nodes · 8K edges, or on a map 3K of 5K nodes placed · 1K of 8K edges. It adds · M hidden by other filters when the canvas's pick holds vertices another filter hides.
    • GraphLegend is a key (glyph and name) with no counts.
    • A semi-join's chip is its label alone: Lasso, a search, a rule's name.
    • GraphToolbar has no count box.
    • The automatic dashboard no longer opens with a Rows tile.
  • The dot grid can be seen. It was drawn in --border, which vanished on a light page and in any recording. It uses --input now.
  • Chat holds the answer's place. From send to the model's first word, Thinking… shows where the answer will be (translations.waiting).
  • A new message brings the conversation to its end. A question sent after scrolling up, or after a click that scrolled an answer card, landed below the fold. A streaming answer still leaves a reader who scrolled up alone.

What to change

  • To have the page's count, give FilterBar a relation (and rowNoun).
  • A test that read N match from GraphCounts, a count from the legend, · N selected from a semi-join's chip, or the toolbar's In full colour group reads FilterBarHarness.readout() instead.
  • GraphCanvasHarness.counts() reads the new GraphCounts sentence.

Every @kanzo-tech/* is published at 0.42.0; move them together (they pin each other exactly).

v0.41.0

Choose a tag to compare

@angel-ip angel-ip released this 11 Oct 17:12
c2d22a5

v0.41.0

A quieter relation picker, charts that let go of their queries, and a filter-bar harness that never presses the wrong chip. Breaking: see What to change.

Highlights

  • RelationPicker no longer writes a grain line (One row per Comment · 151,043 rows · …) under its chips: each hop's fan-out in Add related… already says what it does to the rows.
  • Charts let go of their marks: a chart rebuilt (a resize, a theme change) or unmounted no longer leaves its old marks connected to the coordinator, querying on every filter for a plot nobody sees. Every filter and the timeline's Play get faster.
  • FilterBarHarness.remove(field) takes off only the chips of field: under Playwright it could press the chip after them (a dashboard's pick, or Clear every filter) while the page was still letting the first one go. It now presses each chip by its name, once, and waits for a chip of field to show before giving up.

What to change

  • RelationPickerHarness.grain() is removed: read the path with path().

Every @kanzo-tech/* is published at 0.41.0; move them together (they pin each other exactly).