Thinking in Goldar
Goldar is easier to understand when you begin with the request and the document it should produce, not with a long-lived component tree. Routes coordinate work, resources own reads and mutations, templates own presentation, and browser code is an optional enhancement.
Start with working HTML
The first response should already contain the page’s meaningful content and controls. Use links for navigation and forms for mutations. A user, crawler, or test can interact with that foundation before custom elements load—or when JavaScript does not load at all.
This is not a ban on browser behavior. It is an ordering rule: deliver the document first, then add the smallest client-side capability that improves it.
A route owns one request graph
A Router maps an absolute path to a Route constructor. For every matched request, Goldar creates
a fresh route and its owned resources, initializes its stores, loads its data, and renders the
result.
The route is the composition boundary for that work:
Dataowns reads.FormActionowns POST mutations.Storeholds mutable state used during the request.render()turns resolved values and action references into views or responses.
Resources are addressed by identity rather than by string names at the call site. A route can only read data and inspect actions that it owns, so crossing a route boundary fails explicitly.
class DashboardRoute extends Route {
readonly data = {
account: new AccountData(),
projects: new ProjectsData(),
};
render(context: RouteContext) {
return DashboardView({
account: context.data(this.data.account),
projects: context.data(this.data.projects),
});
}
}
All data owned by the route starts loading concurrently before render() runs. context.data()
returns the resolved value; do not pass the Data instance itself into a view.
Reads belong in Data
Put a request-dependent read in a Data<Output> subclass. Its load() method receives the request
URL, parameters, abort signal, and underlying Hono context.
class ProjectData extends Data<Project> {
load({ params }: DataContext) {
return projectRepository.find(params.require("projectId"));
}
}
This keeps loading observable to the framework and replaceable in tests. It also keeps templates free of networking and persistence concerns.
Goldar does not currently turn Data into a browser-side reactive cache. A new request receives a
new route graph; durable state belongs in the database, service, or platform primitive that owns it.
Mutations belong in FormAction
A FormAction receives one submitted form, validates it, performs the mutation, and returns an
explicit result. It can preserve field errors, redirect, rerender after success, or return a raw
Response.
class RenameProject extends FormAction {
async submit({ form, invalid, redirect }: FormActionContext) {
const name = form.string("name").trim();
if (name.length === 0) {
return invalid({ name: "Enter a project name." });
}
await projectRepository.rename(name);
return redirect("/projects");
}
}
The route exposes an owned action to the template with context.action(action). That reference
contains the form method, URL, identifier, and hidden-field name. After an invalid result,
context.submission(action) returns the preserved values and field errors for the same action.
Same-origin CSRF protection is enabled by default. Disabling it changes the application’s security boundary and should be an explicit adapter-level decision.
Templates own presentation
A compiled .hbs import is a function. Calling it with props returns an opaque View that a route
can return or pass into another template.
const page = ProjectView({ project });
return LayoutView({ title: project.name }, page);
{{outlet}} renders those child views in order without inserting a wrapper element. Imported
templates provide smaller typed presentation units inside another template.
Keep conditionals, empty states, iteration, and presentation composition in templates. Keep authorization, data access, mutations, response status, and nontrivial application decisions in routes and resources.
Expressions and normal attributes are escaped. Rendering trusted HTML requires the explicit
TrustedHtml brand, and branding a string does not sanitize it. Establish the sanitization boundary
before creating that value.
Browser behavior is an island
Use a custom element when a part of the document needs local browser state or interaction. Keep useful light-DOM content inside the element so the server response remains understandable.
When a template contains <quantity-stepper>, Goldar discovers one matching local
quantity-stepper.ts module whose default export is its HTMLElement constructor. The generated
browser entry registers it. Missing or ambiguous modules fail at build time rather than becoming a
silent runtime omission.
Goldar does not currently provide client routing, hydration, or browser-lived reactive stores. Those are not hidden defaults; they are outside the implemented contract.
Hono is the HTTP escape hatch
Goldar assembles a Hono application rather than hiding HTTP behind a closed abstraction.
RouteContext.hono exposes the current Hono context, and the Application server configuration
hook can install middleware before routes are registered.
Return a standard Response when a route is not an HTML page. Use the framework primitives when
they improve ownership and testing, and use the web platform or Hono directly when they are the
clearer boundary.
A translation for component-framework users
| Familiar concept | Goldar equivalent |
|---|---|
| Route loader | Route-owned Data |
| Server action | Route-owned FormAction |
| Render function | Callable compiled .hbs view |
| Stateful browser component | Custom element |
| Request-local service or state | Store |
| Framework root | Application with a Router |
The important difference is lifetime. A Goldar route, its data, its actions, and its stores belong to a request. A custom element belongs to the browser document. Treating those as separate systems keeps server authority and client behavior legible.
A practical decision order
When adding a feature, ask these questions in order:
- What useful HTML should the request return?
- Which route owns the URL and response?
- Which reads belong in
Data? - Which form submissions belong in
FormAction? - Which presentation belongs in typed templates?
- What interaction genuinely requires a custom element?
Continue with Typed Handlebars to build the presentation layer, or use the runtime reference for the exact lifecycle and result contracts.