Repository navigation
Releases: Kanzo-Tech/ui
Release list
v0.49.0
v0.48.0
v0.47.0
v0.46.0
@kanzo-tech/ui (minor)
- Mosaic has one door:
@kanzo-tech/mosaic.@kanzo-tech/ui/analyticsnow exports only the charts and their parts.-
Breaking: import
engine,Engine,Coordinator,Selection,JoinGraph,Relation,RelationExpr,relationKeyand the otherrelation*helpers,numbers,queryFailure,Query,TableExpr,ExprValue,FilterExpr,MosaicClient,makeClient, everyclause*,onRelation, theload*loaders, the aggregates,sql,ascanddescfrom@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";
binand the six axis marks (axisX,axisY,axisFx,axisFy,gridFx,gridFy) stay on/analytics.@kanzo-tech/uino longer lists@uwdata/mosaic-coreand@uwdata/mosaic-sqlas 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 itDashboardandAnswerCard) reads a relation'sSUMMARIZEonce 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-query5 and render oneQueryClientProviderabove 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.queryFieldStatsis gone: useuseFieldStats, or runSUMMARIZEyourself and pass the rows tofieldStats. Clear your client on sign-out.
- Breaking: install
- Breaking:
valibotis an optional peer of@kanzo-tech/ui/analyticsrather than a dependency of@kanzo-tech/ui. Install it if you use/analytics; an app that never imports/analyticsno longer installs it. - Every component takes the words it draws through one prop,
translations. ThePreferencesparts take theirs once onPreferencesRoot, and a part's owntranslationswin for that part. A translated panel no longer has English left in it.- Breaking: pass
translations, notcopy, toPreferencesPanel,PreferencesSectionsandPref. The type isPreferencesTranslations(wasPreferencesCopy).PreferencesPanel'stitleandhintare entries of itstranslations. The retired-theme notice's words areretiredTitleandretired(theme)(wereretired: { title, formatDescription }). - Breaking: pass
translations, notlabels, toFindingsRoot,FindingsItem,FindingsGroupandFindingsTally. The type isFindingsTranslations(wasFindingLabels). - Breaking:
MadeWith'sprefix,infixandloveLabelare entries of itstranslations:<MadeWith translations={{ prefix: "Hecho con", infix: "en", loveLabel: "amor" }}>.
- Breaking: pass
Problemtakes the host's words for a code throughdescribe(code, data), which returns aProblemDescription({ title?, detail?, link?, page? }, the same fields as before).translationsstays for the wordsProblemdraws itself.- Breaking: rename
copy=todescribe=onProblem, and the typeProblemCopytoProblemDescription. The function you pass is unchanged.
- Breaking: rename
RelationPickerinflects 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 oftranslations(RelationPickerTranslations, from@kanzo-tech/ui/analytics), including the two sentence patternsout(to, label)andin(source, label, destination).- Breaking:
pluralis gone. A sentence that reads wrong isphrase(edge, direction)'s to reword, ortranslations.in's. A test that found a hop by its sentence reads the new one:RelationPickerHarness.add("Comment · located in this place").
- Breaking:
- One facet item.
DataTableFacetFilterandChartFiltertakeitemsofFacetFilterItem({ value, label?, icon?, count? }, from@kanzo-tech/ui), asFacetFilteralready did.DataTableFacetFilterkeeps an item's owncountand gives an item without one the column's count.- Breaking: pass
items, notoptions.DataTableFacetOptionandChartFilterOptionare gone; type your items asFacetFilterItem. AChartFilteritem is an object with a stringvalue({ value: "3" }, not a bare3).
- Breaking: pass
- Breaking:
ChartPickX,ChartPickY,ChartPickColor,ChartBrushX,ChartBrushYandChartBrushXYare gone from/analytics. WriteChartToggleX,ChartToggleY,ChartToggleColor,ChartIntervalX,ChartIntervalYandChartIntervalXY; they were the same components under a second name. - Breaking:
@kanzo-tech/ui/tableno longer re-exports TanStack'sTabletype, becauseTablefrom@kanzo-tech/uiis the markup component. Writeimport type { Table } from "@tanstack/react-table"where you name the instanceuseDataTablereturns. Preferences,ThemeNoticeandFloatingPanelleave 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>.PreferencesPropsis gone. - Breaking:
ThemeNotice,ThemeNoticePropsandThemeRetiredCopyare gone. ReaduseKanzoTheme().retiredThemeand toast it once, as/docs/hooks/use-kanzo-theme#the-retired-theme-as-a-toastdoes. - Breaking:
FloatingPanel,FloatingPanelResizeHandleand their props are gone. Copy/docs/layout/floating-panel's example.
- Breaking: replace
- Six names no product called leave the package:
KanzoThemeandKanzoThemeProps. A region that wears another theme isdata-theme="<theme>"and the side's class on an element of yours (/docs/themes#theming-a-region).KanzoThemeProviderstays the one themer.KanzoThemeName, which wasstring. Writestring.safeParseColor.ColorPickerstill takes a blank or half-typedvalue; for a parse of your own, wrapparseColorin atry.useFindings. Read the findings where you passed them toFindingsRoot.- From
/analytics,parseDashboardandrecommend(withRecommendationandRecommendIntent). Read one relation's spec withparseDashboards({ byRelation: { [relation]: saved } });autoDashboardis the recommender's door.
- A question is one part:
Question, withQuestionPrompt,QuestionDescription,QuestionChoices,QuestionChoice,QuestionInput,QuestionTextareaandQuestionError. It works on its own, as a guided chat draws a decision in a turn, and inside aQuestionnaire, which is now only the sequence around its questions: the root,QuestionnaireProgressand the actions. On its own a question holds its answer asvalue,defaultValueandonValueChange({ value }), and a pick answers it in one press; inside aQuestionnairethe answer is the questionnaire's, under the question'sname.nameis also the form field's name, so a question in a plain<form>submits its answer.- Breaking: inside a
Questionnaire,QuestionnaireItemisQuestion,QuestionnaireTitleisQuestionPrompt,QuestionnaireDescriptionisQuestionDescription,QuestionnaireChoicesisQuestionChoices,QuestionnaireChoiceisQuestionChoice,QuestionnaireInputisQuestionInput,QuestionnaireTextareaisQuestionTextareaandQuestionnaireErrorisQuestionError, with the same props.useQuestionnaireItemisuseQuestion, which also carriesanswerandsetAnswer. The types follow:QuestionnaireItemProps,QuestionnaireChoicesPropsandQuestionnaireChoicePropsareQuestionProps,QuestionChoicesPropsandQuestionChoiceProps, andQuestionnaireAnswerisQuestionAnswer. - Breaking:
useQuestionnaire()carriesanswers,questions,stepsandtranslations; itsblocked,indexOf,setAnswerandshortcutsare gone. Answer from inside a question withuseQuestion().setAnswer. - Breaking: the parts'
data-slots follow their names:questionnaire-itemisquestion,questionnaire-titleisquestion-prompt, and every otherquestionnaire-…slot of a question's part isquestion-….
- Breaking: inside a
QuestionnaireandQuestiontake their words throughtranslations.Questionnaire's areprogress(position, count),navigation(the actions row's accessible name),previous,skip,next,submit, and its questions'unanswered, which reach every question inside it; aQuestion's owntranslationswin for that question. An action given children still says those.- Breaking:
QuestionnaireProgresstakes no children. Translate it with<Questionnaire translations={{ progress: (position, count) => `Pregunta ${position} de ${count}` }}>, whicharia-valuetextreads too.
- Breaking:
- A dashboard tile carries its own question:
where, conditions under the page's filter ({ field, in },{ field, between }or{ field, contains }, theConditiontype; a field is a path from the relation's root, crossed in when it leaves the relation), andtop, 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;TileViewdraws a tile of any kind, withactionsin its header.TILE_ROWS,TileContext,TileQuery,Condition,ConditionValueandTileViewPropsare exported from@kanzo-tech/ui/analytics. A tile naming a field or path its relation cannot read draws what it lacks.- Breaking:
measureExprandbucketExprare no longer exported: read a tile's measure and rows throughtileQuery. A host that reads til...
- Breaking:
v0.45.1
@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
@kanzo-tech/llm (minor)
- A local model's load is something you read.
localModelLoad(id)from@kanzo-tech/llm/localanswers where the download and load stand:{ state: "idle" },{ state: "loading", progress, text },{ state: "ready" }or{ state: "failed", error }. The type isModelLoad, on@kanzo-tech/llm. It answers the same whichever screen started the load.watchLocalModel(id, listener)says when it changes, souseSyncExternalStore((l) => watchLocalModel(id, l), () => localModelLoad(id))is the whole subscription. - Breaking:
LocalModelSettings.onProgressis gone. A host that kept its own store from it readslocalModelLoadinstead, and draws it with@kanzo-tech/ai'sModelBadge.
@kanzo-tech/ai (minor)
-
The parts a guided chat is built from, beside the transcript and the composer:
Question,QuestionPrompt,QuestionDescription,QuestionOptionsandQuestionOptionput 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, andonValueChangeis the answer, in one press. A Task'sDecisionhas this shape.Workingsays work is under way: a spinner and its words in arole="status", with@kanzo-tech/ui'sProgressbeside 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 aProblemwith a retry (no retry forai/unavailable). Give itlocalModelLoad's value.
These replace a decision drawn with
RadioGroup, a spinner beside a sentence, or a model-loading bar of your own. -
ConversationScrollButtonno 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),Conversationmeasures again and takes the pin back. -
ChatandAssistdraw a model's offers with@kanzo-tech/ui'sSuggestions.
@kanzo-tech/ui (minor)
Suggestionstakespending: that many pills in skeleton after the ones already there, and the strip staysaria-busyuntil 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-darkcarries its brand at the same weight askanzo:--primaryand--ringare#5cb572rather than the neon#1ae973.- The first chart series,
--chart-1, is the brand's green#52b770rather 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
@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 — itsoverrides(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,GatewayandGatewaySettingsare removed, and with them the dependency on@ai-sdk/openai-compatible. The model islocalModel, from@kanzo-tech/llm/local, in the page; any otherLanguageModelstill works everywhere one is taken.AiErrorhas one code left,ai/unavailable(data.reason);ai/silentandai/rate-limited, and theafter,retryAfterandstatusdata, 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,ConversationContentandConversationScrollButton— the scroll region that follows new content only while the reader is at the end, and the button that takes them back — andPromptInput,PromptInputTextarea,PromptInputToolbarandPromptInputSubmit(PromptInputSubmitProps), the composer where Enter sends, Shift+Enter is a newline and the button is Send, Stop or Retry by the chat'sstatus. A transcript that scrolls itself to the bottom on every turn, or a composer built fromInputGroup, 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.yamlandservices/compose.dev.yamlinclude 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 withlocalModelfrom@kanzo-tech/llm/local. The realm no longer registers theai-gatewayAPI, and the realm-wideapisvariable and output are gone with it: an application's declaration lists noapis, only its ownapi. Removeapis: [ai-gateway]from yourx-application, and the/api/aimount fromkanzoAuth'sapis.
@kanzo-tech/ui (patch)
TreeViewno 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. AclassNamethat restyled the focused row throughdata-focus:should usefocus-visible:.
v0.43.0
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).defineTaskdeclares what a model may answer and which oracles check it.runTaskasks for it, sends the oracles' diagnostics back for a repair, and answers aProposal: accepted, with its diagnostics, or with aDecisionfor the person.proposeasks for several at once and keeps those the oracles accept.verifychecks a value no model wrote. A Task can declare agrammarinstead of anoutputschema when its artefact is text in a language.useTaskis 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 ofask, its answer is the sameanswerpartAnswerCarddraws, and the reply is written from the rows, not by the model. When two relations answer a question equally, the chat asks which, numbered.askOffersgives the questions to start from, each one checked. - Chips on an answer.
AskChipsdraws 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 asai/unavailable. A call can hold the model to a grammar withproviderOptions: { kanzo: { grammar } }.
What to change
useAgentChat(dataAgent(…))→useAskChat({ model, engine, graph, types, selection }).typesis each type alone:readAnswerRelations(coordinator, graph, answerRelationsOf(graph, { hops: 0 })).dataSuggestions→askOffers(a promise, not a stream).dataAgent,dataInstructions,DataAgentOptions,DataSuggestionsOptions,DataToolsand thecontext: { tokens, reserve }option are gone.Chat'ssuggestions/suggesting→proposals/proposing.ChatSkeleton'ssuggestions→proposals.suggest()andSuggestOptionsare gone. Questions to start from are a Task's, throughpropose.AiError'sai/contextcode, and itstokensandbudgetdata, 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
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
FilterBarreadout, at the start of the filter row: 611 of 1,528 people. This is how dc.js'sdataCountand Kibana Discover's hit count do it.GraphCountssays 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.GraphLegendis a key (glyph and name) with no counts.- A semi-join's chip is its label alone: Lasso, a search, a rule's name.
GraphToolbarhas 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--inputnow. Chatholds 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
FilterBararelation(androwNoun). - 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 readsFilterBarHarness.readout()instead. GraphCanvasHarness.counts()reads the newGraphCountssentence.
Every @kanzo-tech/* is published at 0.42.0; move them together (they pin each other exactly).
v0.41.0
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 offield: 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 offieldto show before giving up.
What to change
RelationPickerHarness.grain()is removed: read the path withpath().
Every @kanzo-tech/* is published at 0.41.0; move them together (they pin each other exactly).