A working list of canonical Seaside apps to port. The point is not to clone Seaside exactly — it's to drive the framework with concrete user stories so we can spot:
stube.core),Examples already in the tree:
| file | seaside analogue | what it exercises |
|---|---|---|
guess.clj | WAConvenienceTest-style | defflow, linear s/await, end |
multicounter.clj | WAMultiCounter | :children, s/render-slot, morph-by-id |
wizard.clj | task with back | hand-rolled flow, child→parent ::back |
seaside_todo.clj | HPI tutorial ToDo app | login/register task, filters, Magritte-like descriptions |
calc.clj | calculator demo | dense structured click routing |
dialogs.clj | WAYesOrNoDialog / etc. | stock call/answer dialog helpers |
tabs.clj | WASimpleNavigation | inactive embedded child preservation |
calendar.clj | WAMiniCalendar | grid rendering, structured cell payloads |
todo.clj | WATodo | slot-local in-place editing |
paginated_list.clj | WABatchedList | pagination state, EDN-safe render callback |
table_report.clj | WATableReport | EDN column maps, click-to-sort |
tree.clj | WATree | recursive render, expansion set |
breadcrumb.clj | WAPath / WATrail | s/decorate end-to-end |
example_browser.clj | WAExampleBrowser | mount/registry lookup, detail child swap |
file_upload.clj | WAFileUploadExample | multipart route, upload event |
clock.clj | WAClock / WATurboCounter | scheduled events, timer ownership |
shared_counter.clj | CTCounter / CTReport | pub/sub delivery to live conversations |
chat.clj | CTChat | multi-user topic updates |
protected_counter.clj | WASessionProtectedCounter | app login + cid owner cookie |
seaside_todo.clj ports the tutorial's running application as a
single stube example mounted at /seaside-todo. It intentionally
keeps the Seaside chapter names visible in the code:
StUser / StTask → plain EDN maps and pure helpers.StRootTask → a hand-rolled task component that :calls login,
register, and logged-in screens.StLoginComponent / StRegisterComponent / StTaskEditor →
answering components.StMenuComponent → a reusable menu component whose entries route
EDN events to the parent instead of holding Smalltalk callback
blocks.StImageDatabase → an EDN :db value carried by the root task, so
the example remains pure and file-store friendly.Findings from the port:
/atomTasks), while
stube currently only mounts component shells plus conversation
endpoints. That is the clearest missing primitive if we want to port
the book literally end to end.These ship in this commit. Each one was picked because it stresses the existing kernel along an axis we hadn't really touched yet, and because it points at a small DX win or a clearly-scoped follow-up.
calc.clj — Calculator (Seaside book demo)s/on self :click :as :foo ergonomics.(s/on self :click :as :digit-7) ×10 for a
number pad is repetitive. Worth considering whether a :click :as [:digit n] form (route a structured event instead of one keyword
per button) belongs in the helper. Open question, no commit.dialogs.clj — Confirm / Prompt / Choose (WAYesOrNoDialog,WAInputDialog, WAChoiceDialog)
confirm:, request: and chooseFrom: on WAComponent.confirm/prompt/choose
functions that wrap s/embed. If they prove useful across examples
they should graduate into stube.core.tabs.clj — Tabbed Navigation (WASimpleNavigation):conv/instances.:instance/children more than multicounter
does (multicounter renders all three children every time).calendar.clj — Mini calendar picker (WAMiniCalendar):answers
back the chosen LocalDate.:click :as :keyword route can't easily
carry "which cell". The example sidesteps this by using one
per-day signal name; the cleaner long-term path is to allow (s/on self :click :as [:pick-day day-of-month]) and have the kernel
route into a multi-arity resume / handler.todo.clj — Todo list with in-place editor (WATodo + WATodoItemWATodoItemEditor)self call: editor
from inside the child, which works because call: swaps the
receiver in its parent's render slot.[:call-in-slot :slot/editor ... :resume :on-edit]. The parent uses
:editing-id only to choose which row renders that slot; the edit
form itself is a real component that answers back.:call-in-slot is the small primitive that makes this
faithful without a client-side island or parent-owned form state.These shipped as a full sweep after the Tier 1 helper surface hardened. No new primitive was needed; each demo is ordinary component state, structured events, embedding, or decoration.
paginated_list.clj — Batched list (WABatchedList):items, :page-size, and
a row renderer supplied at init.table_report.clj — Sortable report (WATableReport)[:sort column-id] to one handler.tree.clj — Expandable tree (WATree / class browser):expanded
set of node ids.breadcrumb.clj — Breadcrumb trail (WAPath / WATrail)s/decorate end to end.example_browser.clj — Example browser (WAExampleBrowser)(s/mounts) and (s/help flow-id) at render time.:call, not
structural embedding.These demos drove the first async / non-SSE surface. The design rule was to keep conversations cid/iid-scoped and EDN-clean: timers deliver normal events, topic publications deliver normal events, and uploads are summarised as data before handlers store anything.
For the multi-tab demos, "open the same URL twice" means the standalone
mount URL (/chat, /shared-counter). stube intentionally mints a
fresh conversation id for each visit; the sharing happens through
example-level app state plus topic delivery, not by reusing a Seaside-like
session URL.
file_upload.clj — File upload (WAFileUploadExample)/stube/upload/:cid/:iid, targeted at a hidden iframe so the Datastar
shell does not navigate away.(s/upload-attrs self) and (s/upload-frame self)
render the zero-JS form plumbing. The HTTP layer parses multipart
params and dispatches :upload-received to the target instance.java.io.File values. The event carries filename/content-type/size
and a temporary path string for immediate handler use; the demo stores
only safe summaries and deletes temp files via :io.clock.clj — Clock / turbo counter (WAClock, WATurboCounter):start emits (s/after ms event); each tick
updates state and schedules the next tick if still running.[:after delay-ms route-event], re-exported as
(s/after delay-ms route-event). The server binds a scheduler while
folding kernel effects; scheduled events are cid/iid-scoped and are
cancelled or dropped when the conversation/instance disappears.shared_counter.clj — Shared counter/report (CTCounter, CTReport)(s/subscribe topic event) / (s/unsubscribe topic) effects plus (s/publish! topic msg). Publication is
asynchronous and dispatches event with msg as :payload to every
live subscriber.chat.clj — Multi-user chat (CTChat)protected_counter.clj — Session-protected counter(WASessionProtectedCounter)
stube_sid owner cookie and rejects cross-session
POSTs. The authenticated app principal is ordinary conversation
state in this example; host applications with real auth should pass or
verify principals at their own boundary.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |