Skip to content

refactor: take one product's concepts back out of the package - #6

Merged
inureyes merged 1 commit into
mainfrom
refactor/product-concepts-out
Aug 6, 2026
Merged

refactor: take one product's concepts back out of the package#6
inureyes merged 1 commit into
mainfrom
refactor/product-concepts-out

Conversation

@inureyes

@inureyes inureyes commented Aug 6, 2026

Copy link
Copy Markdown
Member

What

Four places where one product's concepts had come across with the code. Found by re-reading the package against a single question: is this a rendering component, or does it need to know something only a product knows?

Component What it knew that it should not
DataTable Where column preferences are stored
ErrorState That "model" is a kind of error
EmptyState That a product has Creations, Benchmark and Logs screens
Tabs A four-name guide vocabulary, half of which lived in the consumer

All four arrived the same way: by lifting product files rather than designing an interface. The sibling @lablup/ui-ai, whose components were written against view models (AiToolCallView, AiRenderAdapters) from the start, has none of them. That difference is the lesson worth keeping.

The four

DataTable wrote to localStorage itself. Where column preferences live, under which key, per user or per workspace, or whether they persist at all, is a decision only a consumer can make. A component that answers it cannot be reused by a consumer that answers differently, and it also stopped the table working anywhere localStorage is absent. persistKey is replaced by columnState + onColumnStateChange. Two new tests: the component touches no storage, and it does not report back the state it was handed, so a caller that persists on change and feeds the result in cannot loop.

ErrorState took an error category. "network" | "configuration" | "model" | "permission" | "generic". Beyond "model" being one product's vocabulary, the five names resolved to three colours: network and permission were the same amber, model and generic the same red. It now takes tone: "danger" | "warning" | "accent" and an optional icon.

EmptyState shipped ten drawings named after one product's screens. No other consumer has a "creations" screen to draw for. illustration is a ReactNode now and the drawings go back to the product that owns those screens. This reverses the export added in 0.1.0-alpha.3, which unblocked that product's migration by widening the wrong side of the boundary. Doing it that way was the expedient move at the time; this is the correct one.

Tabs carried a guide-tag system. That one had already announced itself: TAG_CONFIG kept the badge variant here while the label text had to be passed in through tagLabels from the consumer's locale bundle. A single concept in two repositories is what a wrong boundary looks like from the inside. tag, required, TabTagType, TabTagLabels, TAG_CONFIG and tagLabels are gone; a consumer renders its own badge through the TabItem.labelExtra slot that already existed, and the .tabs__tag-badge class stays so it still looks right.

Breaking

All four. They land while there is exactly one consumer and no stable release, which is the cheapest this will ever be. Migration table is in the changelog.

Verification

pnpm run verify green, including the external install fixture. Packed files 140 to 134.

Re-read against a single question: is this a rendering component, or does it
need to know something only a product knows. Four answers came back wrong, and
all four had arrived by lifting product files rather than designing an
interface. The sibling AI package, whose components were written against view
models from the start, has none of them.

`DataTable` wrote to `localStorage` itself. Where column preferences live,
under which key, per user or per workspace, or whether they persist at all, is
a decision only a consumer can make; a component that answers it cannot be
reused by one that answers differently, and the table stopped working anywhere
`localStorage` is absent. It now takes `columnState` and reports changes.

`ErrorState` took an error category, one of network, configuration, model,
permission, generic. "model" is one product's vocabulary, and the five names
resolved to three colours anyway. It now takes a tone and an optional icon.

`EmptyState` shipped ten drawings named after one product's screens: chat,
models, creations, benchmark, logs, statistics, schedule. No other consumer has
a "creations" screen to draw for. `illustration` is a node now, and the
drawings go back to the product. This reverses 0.1.0-alpha.3, which unblocked
that product's migration by widening the wrong side of the boundary.

`Tabs` carried a four-name guide-tag system. That one had already announced
itself: the badge variant lived here while the label text had to be passed in
from the consumer's locale bundle, so a single concept sat in two repositories.
A consumer renders its badge through the `labelExtra` slot that already
existed.

All four are breaking. They land while there is exactly one consumer and no
stable release, which is the cheapest this will ever be.
@inureyes
inureyes merged commit 0eb390a into main Aug 6, 2026
3 checks passed
@inureyes
inureyes deleted the refactor/product-concepts-out branch August 6, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant