YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
Workspace▾
App & Library▾
Domain▾
Scalar▾
model.Zone.tsx
A Zone is the client part of a page section. The page fetches the data; the Zone puts it into the store and hands each record to a Unit or View that draws it.
Open this file when a page gets a new list or detail section, or when a section needs a modal or live updates. One section comes together in four steps:
- The page starts the fetch.
fetch.init<Model><Suffix>()loads a list andfetch.view<Model>(id)loads one record. The result goes down asinitorview. - The Zone hands it to
Load.UnitsorLoad.View. They fill the store and draw the loading and empty states. - Each record goes to a server component. A row goes to a
Unit, the detail to aView. - Actions and forms live in their own files. A button is a
Util, a form is aTemplate, and state and actions live in the store.
Words used on this page
TermDescription
init
The
<model>Init<Suffix> field of fetch.init<Model><Suffix>(): a ClientInit, awaited or not.view
The
<model>View field of fetch.view<Model>(id): one record as a ClientView, awaited or not.hydrate
Copy a server payload into the client store, so the screen and the store hold the same data.
fetch.slice.<name>
Tells a wrapper or control which model and which list it works with.
Suspense boundary
A spot that shows a fallback until its promise lands, without holding up the rest of the page.
File Convention And Props
A Zone file always starts with
"use client". Its props must be able to cross from server to client: an init or view payload, ids, and a className.Path
apps/<app>/lib/<model>/<Model>.Zone.tsxDatabase and service modules may have one. Scalar modules may not.
First Line
"use client";Always, on line 1 above the imports.
List Props
className · init · slice · <parent>Idinit is a ClientInit. slice goes to the wrappers and controls inside.View Props
className · view · <parent>Idview is a ClientView. The signed-in user comes from st.use.self(), not a prop.A new module starts with this Zone: one list export,
Card, and one detail export, View:apps/koyo/lib/icecreamOrder/IcecreamOrder.Zone.tsx
- Exports are role names. The model comes from the namespace, so a page writes
<IcecreamOrder.Zone.Card />, neverIcecreamOrderCard. ClientInit,ClientViewandClientEdittake either shape. The page may pass the resolved payload or the promise its fetch handed out, and the Zone stays the same.- Declare
interface <Name>Propsright above the component, withclassName?first.



Never type a Zone prop as a
cnst model. A Zone is a client component, so a cnst.IcecreamOrder prop is a class instance crossing the server boundary, and lint (no-model-type-in-util-zone) rejects it. Take an id, or a ClientInit / ClientView, and read the model from the store.List Zone With Load.Units
A list section hands its
init to Load.Units. It fills the store with the rows, draws the loading and empty states, and calls your render function for each row.The page starts the query and passes the promise down without awaiting it:
apps/koyo/page/devApp/[devAppId]/dbBackup.tsx
The Zone gives
Load.Units a row renderer and an empty state:apps/koyo/lib/dbBackup/DbBackup.Zone.tsx
renderItemdraws one row, usually by handing it toUnit.CardorUnit.Abstract.renderEmptyis the empty state, often aModel.NewWrapperor a link-style call to action.Model.NewWrapperdraws only the trigger, so theModel.EditModalbeside it draws the form.- An unawaited promise streams.
Load.Unitsshowsloadingbehind a Suspense boundary of its own, and the rest of the page is sent without waiting.
Load.Units props
initClientInit<"model", LightModel>required
The list payload or its promise, handed down from the page.
renderItem(item, idx) => ReactNode
Draws one row; required unless you pass
renderList.renderList(list: DataList) => ReactNode
Draws the whole list, for grouping, tabs, boards or a custom order.
renderEmpty(() => ReactNode) | falsedefault <Empty />
Draws the no-rows state;
false with renderList draws the empty list instead.emptyReactNode
A ready-made no-rows placeholder that wins over
renderEmpty.loadingReactNodedefault Loading.Skeleton
Shown while a promised
init is pending and while the list reloads.paginationbooleandefault true
Adds a pager on desktop and infinite scroll on mobile.
classNamestring
Classes for the wrapping div, such as a grid layout.
View Zone With Load.View
A detail section hands its
view to Load.View. It puts the record into the store, then passes the full model to your renderView.A pending view promise gets its own boundary, so a slow detail never holds up the layout around it:
apps/koyo/lib/ticket/Ticket.Zone.tsx
- The page passes
ticketView.fetch.viewTicket(ticketId)hands outticketViewandticket. Keepticket, a model instance, on the server. - The signed-in user comes from the store.
st.use.self()replaces aselfprop, which would be acnstmodel crossing the boundary.
Load.View props
viewClientView<"model", Model>required
The detail payload or its promise, handed down from the page.
renderView(model) => ReactNoderequired
Draws the full model, usually as
<Model>.View.General.loadingReactNodedefault Loading.Skeleton
Shown while a promised
view is pending.emptyReactNodedefault <Empty />
Shown when the record came back empty.
classNamestring
Classes for the wrapping div.
noDivboolean
Renders
renderView without the wrapping div.Section Orchestration Zones
Some Zones assemble a whole section: a filter, the list, a create button and a modal. The Zone only wires them together; each piece still lives in its own file.
A board with renderList
renderList receives the whole list, so the Zone can group rows into columns and put controls around them:apps/koyo/lib/ticket/Ticket.Zone.tsx
- The filter is a Util.
Ticket.Util.QueryMakerInSelfowns the control; the Zone only places it. Model.Newis the create button and its form in one.partialseeds the new ticket with the current project.renderEmpty={false}keeps the board up. With no tickets yet, the empty columns and the create button still render.
Cards that open a modal
Model.ViewWrapper makes each card open its record, and one Model.ViewEditModal shows it with an edit button:apps/koyo/lib/dessert/Dessert.Zone.tsx
- One modal serves every card.
Model.ViewWrapperonly opens a record by id; the singleModel.ViewEditModalfor that slice draws it. renderTemplateis required. The modal's edit button swaps the View for this form.- Keep local UI state small.
useStateis for modal-open, draft input or drag state, never server data. - Switch modes with
Tabfromakanjs/ui, placed in the page or a View.Tab.Panelrenders its children as-is, so a serverViewpassed in stays server-rendered.
Live And Dashboard Zones
A Zone can also be a dashboard or a live section, when the whole section follows store state, a subscription or a client-only layout.
A dashboard is a
Load.View over a summary model:apps/koyo/lib/summary/Summary.Zone.tsx
A live section subscribes in an effect and unsubscribes in the effect's cleanup:
apps/koyo/lib/chatRoom/ChatRoom.Zone.tsx
- A live list needs no effect. Declare
.live()on the slice, andLoad.Unitsopens the room and applies each change by itself. useEffectis for subscribe-with-cleanup. An effect that loads data on mount repeats a round trip the server already made;akan quality ssrreports it asclient-mount-load.- Never hand-roll a loading branch.
Load.ViewandLoad.Unitsalready draw the pending and empty states, and the route fetched the data before the first byte.
When To Use Zone
Every piece of a screen has one home. Reach for a Zone when a section needs the store; anything that only draws stays on the server.
File
Server
Client
"use client"
Fetches or draws
page/**/*.tsx
✓
The route shell that reads params, starts
fetch.* and passes the results down.<Model>.Unit.tsx
✓
Draws one row or card from a light model.
<Model>.View.tsx
✓
Draws the full detail of one record.
Holds state or an action
<Model>.Zone.tsx
✓
Composes a page section: Load wrappers, store reads and modals.
<Model>.Template.tsx
✓
Form fields and form fragments, each bound to the store.
<Model>.Util.tsx
✓
Small actions, toolboxes and helpers, such as a filter or a remove button.
<model>.store.ts
✓
State and actions, shipped only in the client bundle.
✓Runs hereNot here
Practical Rules
Five rules keep a Zone small:
- Keep pages thin. Pass server
initorviewdata into a Zone instead of building the section in the page. - Lists use
Load.Units, details useLoad.View. - Drawing goes to Unit and View. A row is a
Unitand the full detail is aView, so a Zone holds almost no markup of its own. - Actions go to Util. Buttons and controls inside a Zone are
Utilcomponents. - Business rules stay out of render code. They belong in service, document, store or constant.
Common mistakes
| Mistake, then the fix |
|---|
| ↳ Do this |
| useEffect(() => { fetch… }, []) |
Fetch in the route and pass the result down as init or view. |
| fetch.initXInY() |
Lint rejects it in a client file; reload with st.do.initXInY() instead. |
| init={fetch.initXInY(id)} |
Pass the field, not the whole handle: init={xInitInY}. |
| <X.Zone.Card list={xListInY} /> |
xListInY holds model instances a client prop refuses, so pass xInitInY. |
| self: cnst.User |
Lint rejects a model prop, so read it with st.use.self() or take an id. |
| useState<Mode>(…) |
Switch modes with Tab in the page or a View, so each panel stays server-rendered. |
| isLoading ? <Spinner /> : … |
Use the loading and empty props of Load.Units and Load.View. |