Capabilities
Activity
activity@1capability contract
On this page
Introduction
A statement is an append-only record that an actor did a verb on an activity with a result. Statements are your applet's audit trail. Because every statement has the same shape, they are also the one export that works identically for every applet on every Rack.
Statements are never updated and never deleted. Re-answering is normal: reporting reads the last answered statement per activity per attempt.
db@1 does not replace this. Statements are what happened, while records are what is currently true.
Declaring Activities
Every question you report on must be declared in your manifest's activities array, and at least one activity is required:
"activities": [
{ "id": "intro.connection", "type": "rating", "label": "How connected do you feel to patients?",
"scale": { "min": 1, "max": 6 }, "sequence": 1 },
{ "id": "rd.corine.reaction", "type": "text", "label": "What stood out to you about Corine's story?",
"segment": "track", "sequence": 2 }
]Each activity accepts the following fields:
| Field | Rules |
|---|---|
id |
Required, max 150, unique. This is the data contract. |
type |
Required, max 100. Drives how reporting renders and exports the value. |
label |
Required, max 1000. Becomes the export column header and the analysis heading. |
scale |
Optional object, for rating and scale types. |
segment |
Optional. Must name a declared segment key, or be null for shared. |
sequence |
Optional integer ≥ 0. Controls export column order; defaults to array position. |
Use stable, dotted, lowercase ids, such as {phase}.{topic}.{question}. An undeclared id still records, but it exports unlabeled and appended after every declared column. That makes it a useful safety net, but never a plan.
Emitting Statements
Typically, you will use the answer method for a committed answer and the progress method for navigation. Of course, you may also emit any statement you like with the statement method:
// The common case: a committed answer.
await Rack.activity.answer('intro.connection', 5);
// Navigation and progress. Fire and forget.
void Rack.activity.progress('rd.chapter_2');
// Anything else.
void Rack.activity.statement({
verb: 'viewed',
activity: { id: 'rd.corine.video', type: 'media' },
result: { duration_seconds: 184 },
context: { autoplay: false },
});answer() emits verb: 'answered' with the value in result.response, which is the only field reporting reads, and fills activity.type from your manifest declaration. progress() emits verb: 'progressed' with activity.type: 'step'.
Verbs must match ^[a-z][a-z0-9_.:-]*$. occurred_at defaults to now, in ISO 8601. You should only pass it yourself when replaying something that genuinely happened earlier.
A statement emitted while staff preview the applet is stored with a preview flag, with or without an attempt, so the audit trail can leave it out. Your applet does nothing different.
Reading Your Declarations
You may read your own declarations back from launch data whenever you need them:
Rack.activity.activities(); // LaunchActivity[]
Rack.activity.activity('intro.connection'); // { id, type, label, scale, … } | nullRendering a question from its declared label and scale keeps the participant's wording and the export header identical by construction. It is a small discipline that pays for itself the first time someone edits a question.
Activity Types
type is free text, but reporting understands these types:
type |
result.response |
Used for |
|---|---|---|
text |
string | Free text. Eligible for response analysis. |
rating |
number within scale |
Likert or self-rating. Eligible for metrics. |
scale |
number within scale |
Semantic alias of rating. |
choice |
string key, or array of keys | Single or multi-select. |
custom |
any JSON | Anything else. Exported JSON-encoded. |
Metrics
A metric pairs a pre and a post rating of the same thing, so reporting can show a single shift rather than two columns to eyeball. You may declare metrics in your manifest's reporting block:
"reporting": {
"metrics": [
{ "key": "connection", "label": "Connection",
"pre": "intro.connection", "post": "outro.connection" }
]
}pre and post must be declared activity ids. Metrics require attempt@1, because they are computed over completed attempts. Both activities should use the same scale, or the shift means nothing.
Designing for the Export
Because every statement has the same shape, a Rack reports every applet's answers the same way, with one column per activity, and your declarations decide what that export has to work with. That yields three rules worth writing on the wall:
idis the data contract. Renaming one between versions splits a column: old attempts keep the old column, and new attempts fill a new one.labelis the header. Renaming one is free, and changes only how the column reads.sequenceis the column order. Set it deliberately, so the export reads top to bottom the way the experience does.
Write labels as full sentences, exactly as the participant sees them. Someone reading the spreadsheet six months later has no other copy of the question.