In-app help: guides, tours and field hints
Updated
The help stack has three layers because the reader is standing in three different places: a drawer explains the current screen, a tour points at live controls, and a field hint answers "what goes in this field" without opening a drawer.
The pieces and why they are separate#
| Layer | Source | Runtime surface | Why it exists |
|---|---|---|---|
| ⓘ guide | wiki/guides/<key>.md |
The right drawer opened by InfoButton |
Longer explanation for the screen or tab the user is already on. |
| Step tour | wiki/learn/steps/<scenario>.json |
Show me on screen in a guide drawer | The same click path drives screenshots, docs images and the in-app overlay. |
| Field hint | hint in MetaFormLayout.LayoutJson |
Short text under a form label | Microcopy that must stay visible while the user fills the card. |
The platform architecture page describes the seed path for default guide rows; keep that as the short architectural summary and use this page for the traps and operating rules: in-app guides in architecture.
ⓘ guides from wiki/guides#
A guide file starts with # <Title> and the rest is Markdown. GuideSeeder
embeds the files into ZuloOne.Core as resources and seeds MetaGuide rows on
startup. The key is the file stem, including encoded special characters for grid
keys such as grid.dict%3ACustomer.md.
Seeding is deliberately conservative:
- a missing key is inserted;
- an existing row is upgraded only when it still matches a known previous
default fingerprint (
SHA-256(Title + "\n" + Body)); - a user-edited row is never overwritten by a product upgrade.
Run node tools/check-guide-keys.mjs after adding or renaming a guide. It fails
when a literal guideKey in frontend/src has no file, when a non-grid guide
file is unused, and when the guide tree is empty. --coverage lists
StandardGrid ids that still have no grid.* guide; those are coverage gaps,
not build failures.
The image COPY trap#
The tenant Docker image must copy wiki/guides before publishing Core. MSBuild
EmbeddedResource globs do not fail when they match nothing, so an image can
ship with zero guides and no build error. The Dockerfile copies wiki/guides
explicitly; keep that line with the Core publish context. Do not move guide
files into frontend/public: the app reads seeded MetaGuide rows, not
markdown files from the SPA bundle.
Step manifests drive screenshots and tours#
wiki/learn/steps/*.json is the single source for a documented scenario. The
same manifest is read by:
- the docshots runner, which writes
wiki/img/<shot>.pngfor documentation; frontend/scripts/sync-help-steps.mjs, which copies manifests tofrontend/public/help/stepsfor the SPA;StepTourProvider, which loads/help/steps/index.jsonand highlights the live target when the guide offers Show me on screen.
The tenant Dockerfile copies wiki/learn/steps into the SPA build stage and
asserts that more than the generated index reached dist/help/steps. A
frontend-only build context may lack wiki/; in that case sync-help-steps
writes an empty index and the build has no tours by design. The tenant image is
not allowed to be empty silently.
guideKeys[] is the bridge between a guide and a tour. The resolver also derives
aliases from scenario and owner, but use guideKeys[] whenever the screen
key cannot be derived from the page name, for example a document journal guide
key (grid.journal:<documentTypeMetaId>) or a dictionary list key.
How to add a tour to a screen#
- Add or update the guide in
wiki/guides/<key>.md. - Add or update
wiki/learn/steps/<scenario>.jsonwith stable English UI labels,owner, screenshotshot.namevalues andguideKeys[]for the screen keys that should offer the tour. - If the screen uses a custom component, render
InfoButtonorTabGuideButtonwith that guide key. Standard grids and metadata tabs already have known guide-key conventions. - Run
node tools/check-guide-keys.mjsand the screenshot/image gates. For the SPA asset copy, runnpm --prefix frontend run sync:help-stepsor a build that runs it asprebuild.
Field hints#
Field hints live in the form layout, not in the dictionary or document field
metadata. A field element may override label and set hint; the renderer
passes the resolved hint to the record field shell and shows it under the label.
This is the right place for "required because of this process" text that belongs
on one form layout, not to every use of the field.
Hints are localized with the same inline string-or-object shape as layout labels. Table-part grid columns do not read layout hints: the grid is a region slot, and its column headers come from the table-part metadata. If a line field needs help, put the explanation in the tab guide or redesign the line editor; do not expect a header-field hint to appear in the grid.
Reader feedback#
Under every ⓘ guide body the drawer asks "Was this helpful?" (GuideFeedback),
and every documentation page on getzulo.com asks the same (DocsFeedback). Both
post one anonymous answer — page, yes/no, optional "what were you looking
for?" — to https://getzulo.com/api/cabinet/feedback. The cabinet checks the
browser Origin (getzulo.com, any *.zulo.one tenant, localhost), forwards the
reader's CF-Connecting-IP and passes the vote to the control plane's
POST /api/portal/feedback, rate-limited per address. Operators read it on the
panel's Docs feedback screen, worst pages first.
A guide is recorded as guide:<key>, a site page as its path. The vote goes
from the browser, not through the tenant API: tenant containers have no route
to the panel, and a stand without internet only shows "Could not send".
The page is refused unless it is a site path or a guide: key, and the comment
is cut at 1000 characters — the table cannot become storage for arbitrary text.
Traps#
- A guide key without a markdown file is a dead ⓘ button. The app can open an empty editable guide, but a shipped product should not rely on that.
- A manifest without
guideKeys[]may be invisible. Owner-derived aliases help pages, not every grid or journal key. - A tour is not a test replacement. If a step cannot find a target, the overlay tells the user where to navigate; it does not prove the business flow.
- Do not duplicate guide prose in a developer page. Link to the guide key or
explain the platform rule here; the user's text belongs in
wiki/guides.
What's not there yet#
- Guides are seeded as English defaults. There is no per-language guide body store yet.
- The in-app tour highlights and navigates; it does not execute the full docshots action list for the user.
- Table-part grid cells have no per-column hint surface.
Where next#
- Platform architecture — the short seed-path
description for
MetaGuiderows. - The forms system — localized labels and field hints in
MetaFormLayout. - Documentation pipeline — gates that keep guide keys, screenshots and the portal publishable.