Public pages
This page shows how an App that signs people in greets visitors who have not: a landing page, and, where you allow it, records they can read. Preview.
import { applyTo, commandForm, commandRef, contact, defineApp, faq, field, hero, landing, signIn, step, steps } from 'tablewalk/app';
const applyForFinancing = commandRef('applyForFinancing', { version: 1, input: { applicant_name: field.text({ label: 'Full name', max: 120 }) },});
export default defineApp({ id: 'ignition', name: 'Ignition', sources: { lending: { connection: 'loans', default: true } }, resources: { lead: { access: 'write', owner: 'tenant' }, dealer: { access: 'write', owner: 'platform' }, }, auth: { providers: ['email'], signUp: 'invite', rows: 'tenant', tenancy: { label: 'Dealer', profile: 'dealer', platform: { label: 'Lender' } }, roles: { dealer_staff: { realm: 'tenant', read: ['lead'] } }, }, views: { leads: { kind: 'list', table: 'lead', label: 'Leads', realm: 'tenant' }, finance: commandForm('Apply for financing', applyForFinancing, [step('About you', ['applicant_name'])], { public: true }), welcome: landing( hero('Finance your next car', 'Apply in about five minutes with the quotation from your dealer.', [ applyTo('finance', 'Apply for financing'), signIn('Staff sign in'), ]), [ steps('How it works', [ { title: 'Apply', text: 'You get a reference as soon as you send it.' }, { title: 'A reviewer decides', text: 'You hear about next steps by email.' }, ]), faq('Questions', [ { question: 'Is Ignition a real lender?', answer: 'No. It is a demonstration lender with synthetic data.' }, ]), contact('Contact us', [ ]), ], { label: 'Ignition — car financing', footer: { text: 'A fictional lender.' } }, ), }, home: [{ view: 'welcome', signedOut: true }, { view: 'leads' }], nav: ['leads'],});A landing is a view of kind landing. An App has at most one, it needs
auth, and a { view, signedOut: true } rule in home must open it. The
public form it links to is a public command form.
The whole of Ignition’s landing is examples/apps/ignition/landing.ts.
Where it is served
Section titled “Where it is served”| Address | Shows |
|---|---|
/{app}, signed out |
The landing (the sign-in screen when there is none) |
/{app}, signed in |
The App, on its signed-in home |
/{app}/welcome |
The landing, signed in or not, to preview it |
/{app}/sign-in |
The sign-in screen |
/{app}/form/{key} |
A public form |
What a landing holds
Section titled “What a landing holds”hero(headline, text?, actions, options?) is required: one heading, a line of
text, and one to three calls to action — applyTo(formKey, label) or
signIn(label). Up to twelve sections follow:
| Helper | Holds |
|---|---|
features(title?, items) |
1–6 { title, text } points |
steps(title?, items) |
1–6 numbered steps |
faq(title?, items) |
1–12 { question, answer } disclosures |
text(title?, text) |
Up to 2,000 characters of prose |
callToAction(title, text?, actions) |
A closing prompt with 1–3 actions |
contact(title?, items) |
1–8 { label, href } links |
embed(view, { limit, sort }) |
A cards list view’s records (needs auth.public) |
search(view, filters, opts) |
A search panel over a list’s filters (needs auth.public) |
Everything is plain text, never HTML or Markdown, and every string has a
length limit checked at load. A link is https://, mailto: or tel:;
anything else is refused. landing()’s third argument takes the browser
label and a footer of text and up to eight links.
Without auth.public a landing reads no data: it is served from the App
definition alone, and GET /api/landing is, with public forms and who-am-I,
the only API a signed-out visitor reaches.
Signed-out readers: auth.public
Section titled “Signed-out readers: auth.public”auth.public lets signed-out visitors read named resources, read-only:
auth: { roles: { staff: { read: true } }, public: { read: ['vehicle', 'vehicle_photo', 'dealer', 'site_image'], fields: { dealer: { read: ['id', 'name', 'city', 'phone', 'about'] } }, },},Each resource is named, within the App’s read ceiling, and fields narrows
columns as a role’s grant does. Everything else still needs a session. The
grant is listed in authority.lock, and it needs roles and
rows: 'all'. See signed-out reads.
With it, the landing is drawn inside the App — its menu, its look and a Sign in link — and it can show rows:
- a call to action may open a list or dashboard whose tables the public reads;
embed(view)draws 1–12 records of a cards list;search(view, filters)opens that list with exactly the conditions its filters write, so the result’s address is the search;hero(…, { image, layout, search, highlights })adds a photo (a public file resource’s row),layout: 'split' | 'overlay', a search panel inside the hero, and up to four short highlights.
Signed-out visitors see public record pages as a storefront: no record-kind label, no Back, no Workbench furniture, no history.
slug: true on a resource follows a public record’s id with its name:
/forecourt/vehicles/1-2024-toyota-corolla-le. Only the id is read; any other
spelling answers with a 301 to the current one, and the page carries a real
<title> and a canonical link. Signed-in readers see /forecourt/vehicles/1.
See addresses.
Check it
Section titled “Check it”--check-app --preview-features prints preview: landing at the landing view. Check
the page signed out at /{app} and signed in at /{app}/welcome, at phone
and desk widths. Forecourt’s front page (examples/apps/forecourt/landing.ts)
uses every option above.