Surveys Reference Public

Design evidence

The industry practice and in-repository evidence behind how prompts behave: clickwrap enforceability, survey frequency capping, and the defects fixed during the move.

Guide version: r1 Module version: 1.0.0 Updated: 2026-08-27 Estimated time: 3 min 5 views 100% helpful

Required prompts follow the clickwrap pattern

Agreements presented on screen are only defensible when the user had clear notice, a real chance to read the terms, and took a deliberate action to accept. The recurring failure in disputes is not the wording — it is being unable to show who accepted which version.

That shaped five decisions:

  • The full text sits in the prompt itself, not behind a link.
  • I Agree stays inactive until the text has been scrolled to the end.
  • Nothing is pre-ticked — pre-checked consent is not consent.
  • Every response stores the version, UTC timestamp, IP, user agent and a SHA-256 hash of the text as published.
  • A new version re-asks everyone, which is the standard treatment for materially changed terms.

… but a consent wall must not be a trap

A modal with no way out fails basic accessibility expectations and is hostile besides. So the required prompt keeps a guaranteed exit: I Disagree is always enabled and always reachable by keyboard, focus is moved into the dialog and restored on close, and the dialog traps focus rather than the user. Escape is disabled — the one sanctioned exception, for dialogs where confirming an action is the point.

Declining ends the prompt. Recording a refusal and then continuing to demand an answer would make the record meaningless and the product unusable.

Optional prompts follow in-app survey practice

The consistent finding across in-app survey and NPS guidance is that response rates are destroyed by frequency, not by wording. Users who are asked repeatedly stop reading and start dismissing on reflex. The published advice converges on: keep it to one or two questions, never interrupt a task, snooze for at least a week rather than a day, and cap how often anyone is asked at all.

Hence the defaults: a 7-day snooze, a 30-day cooldown after any answer, an optional hard ceiling on impressions, and a permanent user-controlled opt-out. All four are ignored for required surveys, because those have a different job.

Evidence gathered from the existing codebase

The implementation reuses existing ERPat machinery rather than inventing parallel versions:

Borrowed fromWhat was reused
Tenant AnalyticsThe module shape, and the side-channel main-database connection.
Cron_manager + OverrideResolverMerging a tenant’s own rows with the platform’s in PHP, each badged with its origin — there is no cross-database SQL union.
The support-access bannerThe pattern for injecting something onto every authenticated page, with inline CSS/JS.
Village documentsAn acknowledgement-required flag plus a per-user acknowledgement ledger.
Organization Hub pollsEnforcing one response per person with a database unique index rather than a check-then-insert.
AnnouncementsThe targeting vocabulary, so operators meet one targeting language across ERPat.

Defects found in the old report, and fixed

Reading the code being replaced turned up four real problems, all corrected during the move:

  • A cross-database N+1. Each rating row triggered its own lookup of the rater, against a table living in a different database — slow, and frequently unable to resolve a name at all. Responses now carry a snapshot, so the list needs no lookups.
  • The Overview ignored its filters, always aggregating globally.
  • One permission did two jobs, so any viewer could edit the app catalog.
  • Duplicate protection was racy — a check-then-insert behind a non-unique index. The new ledger uses a real unique constraint.

Deliberate limitations

  • Prompts are web-only in this release. The mobile client keeps its rating endpoint but is not yet prompted.
  • Only platform operators author surveys; tenant admins cannot survey their own staff.
  • Nothing is emailed. Prompts appear where the user already is.
Was this guide helpful?

Report a content problem