Repository navigation
feat!: v0.9.0 — the request says what it wants; Props decides - #36
Merged
Merged
Conversation
The render flow, stated once: the target selector reads what the request wants, Props is the normal flow, and returning an error breaks it. With no error, the framework renders the component the target Is, else Page. Breaking: - No default target selector. Without WithTargetSelector every request wants Page; htmx apps opt in with HTMXRenderTarget (htmx 1/2) or HTMXv4RenderTarget (htmx 4). - Selection asks the RenderTarget Is for each of the page's components, so custom targets take part; a target that Is several is an error. Is has no side effects, and the "targeted but not handled" error is gone: recognising a target and returning no error renders Page. - Mount checks component parameters for ambiguity too, with Props' results counted as provided. New: - On an unmatched htmx target, Is(OtherPage.Method) matches another page's component by its exact generated id (htmxUnmatchedTarget). - RenderComponent(c, more...) renders several components in order, for updating several regions in one response. - Request args: WithRequestArgs and a RequestArgs() page method register per-request providers, func(*http.Request[, *PageNode]) (T, error), run lazily once per request, nearest page first. Components can now take per-request values and request args as well as Props' results. Lint: idfor checks RenderTarget.Is arguments; a new selector check flags a Mount without WithTargetSelector whose pages' templates use htmx; a Mount whose options end in a spread now builds its tree. Docs: new "How a Request Is Rendered" page with a flow diagram; htmx, DI (request args), API, flows, error handling, skill and CLAUDE.md updated. Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
After the flow diagram, trace one dashboard page through a table of requests (its own components, a standalone component, another page's region, an id nobody owns), then the subtle cases: recognised but not rendered, a page without Page, and a custom selector built on ID. rendering_examples_test.go pins every row and case. Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #36 +/- ##
==========================================
+ Coverage 95.21% 95.32% +0.10%
==========================================
Files 16 17 +1
Lines 1547 1754 +207
==========================================
+ Hits 1473 1672 +199
- Misses 44 49 +5
- Partials 30 33 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Split the "unowned id" row into its two real causes, with fixes: a boosted link inheriting another page's region target (fix the markup; safety-net middleware retargets boosted navigation to body), and a page reusing another page's region id (own the id, or recognise it with Is). The middleware recipe uses only public API and is pinned by a test. Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
A boosted form inside a region posting to an action handler gets a fragment for that region back; retargeting it to body was wrong. Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
Flags, in a page's own gsx component, an id={…Q.M |> id} on an element,
or an hx-target / hx-target:inherited={…Q.M |> target}, where Q.M is
another page's component mounted in the same tree and the page's Props
doesn't handle t.Is(Q.M). Requests from that element come back to the
page targeting an id it has no component for, so it renders its Page
into the region. <hx-partial hx-target> (an out-of-band swap) and other
attributes are not flagged. The call is joined to its attribute through
gsx's /*line*/ directives; templ isn't covered.
On eos at 225904df^ it reports the 12 sites of the 5 pages that commit
fixed; at HEAD, nothing.
Docs: rendering.md's "Seeing it happen" gains a development middleware
that reports a non-boosted partial request targeting another page's
component, pinned by rendering_examples_test.go.
Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
Library (from an independent review): - An unmatched htmx target never Is a method of the served page's own type, so a type mounted twice renders Page for the other mount's region instead of its own component. - Components get the writer, request and target only for parameters of exactly those types, so an existing io.Writer or any parameter is still filled from Props' results or WithArgs as before v0.9.0. - Parameters fill deterministically: exact type first, then the first assignable value in order (Props' results, request values, *PageNode), instead of map order. The Mount check models the same order. - An exact id of another page's component skips the loose matching, so it isn't claimed by a suffix match on the served page. - Request-arg RenderComponent/ErrSkipPageRender are honoured in both extended ServeHTTP forms; a selector error there reaches the handler. - render keeps a Content-Type that Props set; text/html is the default. - Is with a bound value of a generic page type matches (outside generic code); an id -> component index replaces per-request tree walks. Linter: - region no longer flags an element whose own request goes to another page (lazy loads, cross-page forms); covers templ files generated without a version line or with explicit semicolons; compares values as tokens (multi-line, comments); caches template parses per process; reports at the stage name's column; follows the runtime owner of promoted methods. - selector treats WithTargetSelector(nil) as no selector. - idfor wording: "method value"; softer message for non-function Is arguments, which a custom RenderTarget may accept. - route-literal skips the route argument of Mount/Parse calls. Docs: fill order and exact-type rule, Mount check scope, status with a notice (Content-Type first), provider errors, diagram error edges, generic Is limitation, anchors and examples fixed. Claude-Session: https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The new docs page
docs/rendering.md("How a Request Is Rendered") lays out the whole model with a flow diagram. Start the review there.Breaking
WithTargetSelector, every request wantsPage. htmx apps opt in withHTMXRenderTarget(htmx 1/2) orHTMXv4RenderTarget(htmx 4). The four htmx 2 examples now opt in.Isonly. AfterPropsreturns no error, the framework asks the targetIs(m)for each of the page's components and renders the one it matches, elsePage.RenderTargettypes take part in selection.Ishas no side effects, and the "targeted but not handled" error is gone. Recognising a target and returning no error now rendersPage.Mountchecks component parameters for ambiguity, withProps' result types counted as provided.New
Is(OtherPage.Method): on an unmatched htmx target, matches another page's component by its exact generated id (htmxUnmatchedTarget, now inhtmx.go).RenderComponent(c, more...): renders several components in order as one response, for updating several regions at once.WithRequestArgs(providers...)and aRequestArgs() []anypage method.func(*http.Request[, *PageNode]) (T, error).Props' results.Lint (
tools/lint)idforchecksRenderTarget.Isarguments, flagging non-function arguments such ast.Is(p.Content())and unmounted receivers.selectorcheck flags aMountwith noWithTargetSelectorwhen its pages' templates use htmx. It skips_test.gofiles and option lists it can't resolve.regioncheck (.gsx) flags a page's component that puts another page's component id inidorhx-target/hx-target:inherited, chain form included. It isn't flagged when the page'sPropshandlesIsfor that component, and<hx-partial>isn't flagged. On eos at 225904df^ it reports all 12 sites across the 5 pages that commit fixed; at eos HEAD it reports nothing.Mountwhose options end in a spread (opts...) now builds its tree, so those pages are linted. Before, they were silently skipped.Docs
docs/rendering.md, with worked examples aroundIsand a section on targets that belong to another page. That section includes a middleware that retargets boosted navigation tobody, and a development middleware that reports borrowed regions. Both are pinned byrendering_examples_test.go.CLAUDE.md.docs-sitebranch (already pushed,379296a) adds the sidebar entry and the Mermaid plugin. Merging this PR tomaintriggers the deploy.Choices for review
RequestArgsprovider overrides an outer provider of the same type, rather than being an error.RenderComponentnow return severalPropsvalues instead, so they show the normal flow.Verification
go test ./..., golangci-lint and gopls are clean.selectorfinding.tools/linttests pass.go.work: 344 packages ok, 0 failures, and the new linter reports nothing.Release
v0.9.0 and
tools/lint/v0.1.17, after review.https://claude.ai/code/session_01Dp3Jmu2ePyCME2yG9yd672