v0.2.3
Fixes a crash that takes down any form with an array-typed field, plus
two CoreComponents corrections and the missing half of the shipped
manifest.
translate_error/1: no longer raises on non-String.Charserror opts. It stringified every opt eagerly instead of deferring toString.replace/4's function form, so the opts Ecto attaches to a cast error on an{:array, _}field —[type: {:array, :string}, validation: :cast]— raisedProtocol.UndefinedErroron the tuple. Every<.input field={...}>routes errors through this, so the first invalid submit on any form with an array or composite-typed field returned a 500. Same class of crash forvalidate_subset/validate_inclusionopts carrying atom lists. If you worked around this by overridingtranslate_error/1, you can drop the override.button/1:typenow passes through. It was neither a declared attr nor in the:globalinclude list, so<.button type="button">emitted an "undefined attribute" warning and the attribute was dropped — every button inside a form submitted it, with no way to opt out. Omittingtypestill leaves the attribute off, so existing submit buttons are unaffected.formpasses through too.flash/1+flash_group/1: one toast container per page instead of one per notice. Eachflash/1rendered its own fixed-positiontoast toast-top toast-endcontainer, so simultaneous notices — an:infoand an:errorfrom the same action, or a flash plus a disconnect toast — stacked at the identical fixed position and occluded each other. Thetoastcontainer now lives onflash_group/1and every notice stacks inside it. Behavior change:flash/1rendered on its own is now an inlinealertand no longer positions itself. If you callflash/1outsideflash_group/1and relied on it floating, wrap it in your own positioned container. Callingflash_group/1from your root layout — the documented path — needs no change. The container ispointer-events-none(notices re-enable it), so the now-always-present fixed element can't intercept clicks in an empty corner.:infonotices arerole="status"rather than the interruptingrole="alert"; errors stay assertive. Both components now declareattr :class, so a caller class merges into the root instead of colliding with the identity classes.- The install manifest registers all nine shipped wrappers. It
listed only
button,card,icon,input, andflash, while the kit also shipsheader,list,table, andflash_group— shipped, documented, and invisible on/designand in/design.json, so agents following the build order re-wrapped or hand-rolled them.daisy_overrides/0likewise now reports every primitive the kit actually wraps (alert, toast, list, table, and the four input types) instead of just button and card. Existing apps: re-runmix joby_kit.install --forceto pick up the new registrations, or copy the entries into yourDesignManifestby hand. (flash_groupis registered without a preview on purpose — it's a fixed-position container, so an inline preview would float over the design page rather than sit in its card.)
Note for hosts that forked a core component (import JobyKit.CoreComponents, except: [...]): the button/1 and flash/1
fixes above land in the kit's copy, not yours. Apply them to your fork.
v0.2.2
Docs-only release. No functional change — skip it if 0.2.1 is working for you.
- ExDoc no longer tries to link the internal patcher modules
(
JobyKit.AgentsMd,JobyKit.ClaudeMd,JobyKit.NavPatcher,JobyKit.AppCss), which are@moduledoc falseon purpose, orPhoenix.Component.__components__/0, which is hidden upstream. They render as plain code viaskip_code_autolink_to, so the published docs build warning-free.
v0.2.1
Two rendering fixes, both in CoreComponents:
flash/1: every toast gets a usableidagain.attr :idalready puts:idin assigns, so theassign_new/3default never fired and any flash rendered without an explicit id — both toastsflash_group/1shows, i.e. the ones users actually see — rendered with noidattribute and a dismiss handler ofJS.hide(to: "#").#is not a valid selector, so clicking a flash threwFailed to execute 'querySelectorAll' on 'Document'and the toast never faded out. Thelv:clear-flashpush runs first, so the flash still cleared — which is how this stayed hidden behind a working-looking dismiss.table/1: action cell gainswhitespace-nowrap. Wrappable button text let the cell under-report min-content under thew-0width hack, so action buttons painted past the table edge on full-width tables. Host apps carrying a scoped nowrap override for[data-component="JobyKit.CoreComponents.table"]can drop it after upgrading.
v0.2.0
Wrapper-contract enforcement: agent-experience fixes for the failure
mode where an agent skips the manifest, drops raw HTML primitives into
.heex, and gets a green lint check anyway. This release closes that
loop with a real lint rule, an auto-loaded CLAUDE.md, and a worked
composite example. Existing consumers may see new warnings on first
mix joby_kit.lint run after upgrading — that's the rule firing on
pre-existing violations; silence per-line with
<%!-- jobykit:allow-raw-html --%> or lift the markup into a wrapper.
Linter
- New
:raw_html_primitiverule (warning). Scans.heexand~Hblocks in.exfor raw<button>,<input>,<textarea>, and<select>outside of wrapper definitions. Files containingdata-component=are treated as wrapper territory and skipped. Per-line opt-out via<%!-- jobykit:allow-raw-html --%>(heex) or# jobykit:allow-raw-htmlon the preceding.exline. Inside Elixir string literals (heuristically detected) doesn't fire. mix joby_kit.lintdefault--pathsnow includeslib/**/*.heexalongsidelib/**/*.ex.
CLAUDE.md
mix joby_kit.installnow writes aCLAUDE.mdblock at the project root (auto-loaded by Claude Code) that inlines the highest-priority wrapper-contract diagnostics — including "Symptoms you skipped step 1" and the per-line opt-out syntax. Idempotent and marker-bracketed likeAGENTS.md.- New
JobyKit.ClaudeMdpatcher mirrorsJobyKit.AgentsMd.
Worked composite example
mix joby_kit.installnow scaffolds<App>Web.CompositeComponentswith a workingempty_state/1composite (icon + title + supporting text + optional action slot). Pre-registered in the generatedDesignManifestand previewed on/custom-designs. Pattern-match on it when adding your own composites.
Bootstrap stdout
mix joby_kit.new,mix joby_kit.install, andmix joby_kit.bootstrapend-of-run summaries now inline the "Symptoms you skipped step 1" diagnostic (raw<button>/<input>, private function components styled as primitives, components missingdata-component/:rest/ manifest entry). Calling out the failure modes in stdout means an agent doesn't have to know to openAGENTS.mdto find them.
v0.1.1
Documentation + first-run UX fixes for hex consumers:
mix joby_kit.newnow defaults to a hex dep ({:joby_kit, "~> X.Y"}) when no--joby-kit-pathis given. The flag stays for kit development; the README and moduledoc lead with the hex install path (mix archive.install hex joby_kit).README.mdandmix joby_kit.newmoduledoc rewritten to drop references to local checkouts and.ezbuild steps.mix joby_kit.newskips the path-depapp.cssrewrite when running in hex mode (the install task's default@sourcealready resolves for hex deps).
No runtime API changes.
v0.1.0
Initial release. JobyKit ships:
Manifest + design pages
JobyKit.Manifest— behaviour +__using__macro for declaring a host component manifest.category/2andcomponent/3macros register entries;@before_compilegeneratesentries/0,by_category/0,categories/0,category_label/1,category_description/1,fetch/2callbacks. The runtimeenrich/1helper introspects each component's attrs/slots viaPhoenix.Component.__components__/0so prop signatures never drift from source.JobyKit.Contract— universal contract content (5-step build order, 5-rule wrapper checklist, 3-layer module taxonomy) as plain data.JobyKit.DaisyCatalogue— canonical list of every daisyUI primitive (62 entries across 7 daisy categories), with stable atom IDs, default statuses, amerged/1overlay that applies host overrides, and ademo/1function component dispatched per primitive.JobyKit.SignatureComponent— per-component signature card renderer.JobyKit.PageComponent— two function components for the design surfaces:page_component/1— the kit's curated/designpage (filters to:coreonly). Optional:custom_pathattr renders an agent-redirect callout pointing new components to the host's custom-designs page.custom_page_component/1— host's/custom-designspage (renders only non-:coreentries) with a breadcrumb back to the kit page.
JobyKit.ManifestController— JSON endpoint serving the combined manifest (kit core + composites + domain) at/design.json. Reads the manifest module fromconn.private[:joby_kit_manifest].
Core components
JobyKit.CoreComponents— kit-shipped wrappers that hosts import:button/1,card/1,header/1,icon/1,input/1,flash/1,flash_group/1,list/1,table/1. Each carriesdata-component="JobyKit.CoreComponents.<name>", declares attrs withvalues:enums, acceptsattr :rest, :global, and treatsclassas additive on top of the daisy primitive class set. Plusshow/2/hide/2JS helpers and a Gettext-freetranslate_error/1.JobyKit.NavComponent—simple_nav/1, a daisyUI navbar primitive with active-link highlighting. Used in the kit-flavoredLayouts.appgenerated bymix joby_kit.new.
Patchers (idempotent host-file editors)
JobyKit.AgentsMd— patches the host'sAGENTS.mdto (a) append the JobyKit guidelines section and (b) walk a list of rule-rewrites that replace stale Phoenix-default rules now superseded by the kit. Four rewrites ship: anti-daisy line,<.input>source rule,<.input>class-override rule,<.icon>source rule.JobyKit.AppCss— adds@source "../../deps/joby_kit/lib";to the host'sassets/css/app.cssso Tailwind v4 scans the kit for class names.JobyKit.NavPatcher— locates the first<nav>or<header>+</ul>in the host'sapp.html.heex(orlayouts.exas a fallback) and inserts kit nav links wrapped in<%!-- jobykit:nav-* --%>markers. Idempotent.
Linting
JobyKit.Lint— engine that verifies the wrapper contract by introspecting a manifest module and scanning the host's source. Four rules::manifest_drift(entry points at non-existent function),:missing_data_component(registered wrapper missing the attribute),:missing_rest_global(registered wrapper missingattr :rest, :global),:unregistered_wrapper(function emitsdata-componentbut isn't in the manifest).
Mix tasks
mix joby_kit.install— installs into an existing Phoenix project: generatesdesign_manifest.ex,design_previews.ex, the two design LiveViews, patchesAGENTS.md, patchesassets/css/app.css, and patches the host's nav with/design//custom-designslinks.mix joby_kit.bootstrap— composesinstallwith greenfield steps for an already-generatedphx.newproject: replaces the default HomeLive, rewiresrouter.ex, and removes the unused PageController/PageHTML.mix joby_kit.new <app_name>— wrapsmix phx.newto generate a new Phoenix app with JobyKit baked in: replaces<app>_web.ex, layouts, and router with kit-flavored variants; deletes the redundant Phoenix scaffolding; runsmix joby_kit.install; runsmix assets.setupandmix assets.buildsomix phx.serverworks on the first try.mix joby_kit.gen.wrapper <name>— scaffolds a new wrapper component end-to-end: function skeleton with the contract baked in, manifest entry registered, preview function added.mix joby_kit.lint— CLI for the lint engine. Auto-detects the host's manifest, supports--format jsonfor agent consumption, and--strictto fail on warnings.