Tool instance foundation
This prompt builds the structural foundation for tool instances — the live, project-specific running version of a reference tool.
This prompt builds the structural foundation for tool instances — the live, project-specific running version of a reference tool. Read the full codebase before starting, particularly:
- The reference tool nodes and their meta fields (phase, format, location, modality etc)
- The phase node structure (meta.phase, meta.movement, meta.status, meta.selected_tools)
- The existing contribution/artefact flow from Phase 3
- api/projects.js and the node creation patterns established there
This prompt has four distinct pieces. Build and verify each before starting the next.
PIECE 1 — Filter option extensibility
Currently tool filter properties (format, location, modality, duration, tech_level) are hardcoded strings in the tool browser UI. They need to become a database-backed extensible list so new options can be added without code changes.
New database table
CREATE TABLE IF NOT EXISTS filter_options (
id TEXT PRIMARY KEY,
filter_type TEXT NOT NULL, -- "format" | "location" | "modality" | "duration" | "tech_level"
value TEXT NOT NULL, -- "indoors" | "outdoors" | "playing fields" etc
label TEXT NOT NULL, -- display label, may differ from value
created_by TEXT, -- user id who added it, null for seeded options
created_at TEXT NOT NULL,
UNIQUE(filter_type, value)
);
Seed the initial options
const initialOptions = [
// format
{ filter_type: 'format', value: 'individual', label: 'Individual' },
{ filter_type: 'format', value: 'group', label: 'Group' },
// location
{ filter_type: 'location', value: 'indoors', label: 'Indoors' },
{ filter_type: 'location', value: 'outdoors', label: 'Outdoors' },
// modality
{ filter_type: 'modality', value: 'sync', label: 'Synchronous' },
{ filter_type: 'modality', value: 'async', label: 'Asynchronous' },
// duration
{ filter_type: 'duration', value: '<30min', label: 'Under 30 min' },
{ filter_type: 'duration', value: '30-60min', label: '30–60 min' },
{ filter_type: 'duration', value: '60min+', label: '60 min+' },
// tech_level
{ filter_type: 'tech_level', value: 'low', label: 'Low tech' },
{ filter_type: 'tech_level', value: 'high', label: 'High tech' },
];
New API endpoints
GET /api/filter-options
Returns all filter options grouped by filter_type
{ format: [...], location: [...], modality: [...], ... }
Auth: any authenticated user
POST /api/filter-options
Body: { filter_type, value, label }
Creates a new filter option if the (filter_type, value) pair doesn't exist
Auth: institution_admin+ only
Returns: the created option
Frontend changes
- On boot, fetch
/api/filter-optionsand store instate.filterOptions - Replace all hardcoded filter pill arrays in the tool browser with values from
state.filterOptions - In the tool browser filter UI, add a small
+ add filter optionlink at the end of each filter group (visible to institution_admin+ only). Clicking opens a tiny inline form: label input + POST /api/filter-options. On success, refreshes state.filterOptions and re-renders the filter pills. - New options are immediately available when editing any tool node’s filter meta fields
Verify piece 1
- GET /api/filter-options returns all seeded options grouped correctly
- Tool browser filter pills render from the API, not hardcoded
- As institution_admin, add “Playing fields” to location filter type
- “Playing fields” appears as a filter pill in the tool browser immediately
- As student, the “+ add filter option” link is not visible
PIECE 2 — Tool instance node
When a facilitator clicks “Start this phase →” on a phase node (after selecting tools), the system currently sets meta.selected_tools on the phase node. Replace this behaviour: instead of storing selected_tools on the phase node, create a tool instance node as a child of the phase node for each selected tool.
Tool instance node structure
{
"id": "ti_[timestamp]_[random]",
"type": "tool_instance",
"title": "[Tool name] — [Phase] — [Date]",
"parent_id": "[phase_node_id]",
"relations": [
{ "type": "references", "target": "[ref_tool_node_id]" },
{ "type": "contains", "target": "[group_instance_id]" }
],
"meta": {
"phase": "gather",
"movement": "past",
"ref_tool_id": "[ref_tool_node_id]",
"status": "preparing",
"modality": "digital",
"created_by": "[user_id]",
"created_at": "ISO",
"started_at": null,
"completed_at": null,
"steps": [
{
"id": "step_1",
"title": "Step title",
"body": "Step instructions",
"order": 1,
"capture_inline": false
}
],
"local_variations": [],
"group_limit": 5
}
}
Steps are copied from the reference tool node’s body at instance creation time,
parsed into discrete steps. The reference tool body uses a simple convention:
numbered steps separated by newlines. Parse these into the steps array.
If the reference tool body has no numbered steps, create one default step:
{ title: "Run the activity", body: "[full tool body text]", order: 1 }.
local_variations is an empty array at creation. When a facilitator edits a step (title or body) before Start is pressed, record the original and changed values here:
{ "step_id": "step_1", "field": "body", "original": "...", "modified": "...", "modified_at": "ISO" }
This is the variation tracking mechanism for F17.
On creation:
- Copy meta.phase and meta.movement from the parent phase node
- Copy meta.group_limit from the project node (walk up parent chain to find it)
- Fetch reference tool node to get steps and filter meta
- Set status to “preparing”
- Update parent phase node: remove meta.selected_tools (no longer needed), set meta.status = “active”
New API endpoint
POST /api/tool-instances
Body: { phase_node_id, ref_tool_ids: ["id1", "id2"] }
Creates one tool_instance node per ref_tool_id as child of phase_node_id
Returns: { tool_instance_ids: ["ti_...", "ti_..."] }
Auth: facilitator+
Call this endpoint from the “Start this phase →” button instead of the current meta.selected_tools approach.
Verify piece 2
- Select two tools on Past — Gather phase node, click “Start this phase →”
- Two tool_instance nodes appear as children of Past — Gather in the tree
- Each tool_instance node has correct meta: phase, movement, ref_tool_id, status: preparing
- Each has steps array populated from the reference tool body
- relations[] contains a references relation pointing to the correct ref tool node
- Phase node meta.status is now “active”, meta.selected_tools is gone
PIECE 3 — Group instance nodes
When a tool instance is created, generate a default set of group instance nodes as children. The number of groups comes from the class node’s meta.suggested_groups (set during project wizard). If no class is associated or suggested_groups is not set, default to 3 groups.
Group instance node structure
{
"id": "gi_[timestamp]_[random]",
"type": "group_instance",
"title": "Group [n]",
"parent_id": "[tool_instance_id]",
"relations": [
{ "type": "involves", "target": "[tool_instance_id]" }
],
"meta": {
"tool_instance_id": "[tool_instance_id]",
"group_number": 1,
"modality": "digital",
"status": "not_started",
"members": [
{
"name": "",
"role": "",
"person_node_id": null
}
],
"step_trace": [],
"submitted_at": null,
"uploaded_by": null,
"on_behalf_of": null
}
}
members array: starts empty (or with blank slots matching the reference tool’s suggested group size if that field exists on the ref tool node meta). Each member slot has name (free text), role (free text), and optionally a person_node_id if matched to a roster entry.
step_trace records timestamps as the group progresses through steps:
[
{ "step_id": "step_1", "entered_at": "ISO", "exited_at": "ISO" },
{ "step_id": "step_2", "entered_at": "ISO", "exited_at": null }
]
This is the activity trace for the facilitator view (F17 basis).
modality defaults to “digital”. Can be changed to “paper” by the facilitator in the tool instance view before Start is pressed.
on_behalf_of / uploaded_by: for proxy uploads (F16) — when a facilitator uploads content for a paper group, set uploaded_by to the facilitator’s user id and on_behalf_of to the group_instance_id.
Generate group instances
Add group instance creation to POST /api/tool-instances:
After creating the tool_instance node, create N group_instance child nodes.
N = class.meta.suggested_groups or 3 if not available.
Return group_instance_ids in the response.
Verify piece 3
- After “Start this phase →”, expand a tool_instance node in the tree
- See N group_instance child nodes (matching suggested_groups or 3 default)
- Each group_instance has correct meta: tool_instance_id, group_number, modality: digital, status: not_started, empty members array
PIECE 4 — Tool instance view and group management
When a node of type tool_instance is selected, render a dedicated view instead of
the standard parents/content/children layout. This is the facilitator’s control panel
for this tool session.
Tool instance view layout
┌─────────────────────────────────────────────────────────────────┐
│ [Tool name] [● Preparing] [node view ↗] │
│ [Phase] · [Movement] · [Date created] │
│ Reference: [ref tool name →] (clickable, navigates to ref node) │
├─────────────────────────────────────────────────────────────────┤
│ TABS: [ Setup ] [ Groups n ] [ Contributions n ] [ Summary ] │
├─────────────────────────────────────────────────────────────────┤
│ [tab content] │
└─────────────────────────────────────────────────────────────────┘
Status badges:
preparing→ amber · “Preparing”active→ accent · “Active”complete→ muted blue · “Complete”
Setup tab
Shows the tool steps, editable before Start is pressed. Read-only after.
STEPS (editable until started)
1. [Step title — editable input]
[Step body — editable textarea]
[capture inline: ○ yes ● no]
[↑ move up] [↓ move down] [+ insert after] [× remove]
2. ...
[+ Add step]
──────────────────────────────────────────────
RESOURCES
Format: group · Indoors · Synchronous · 60min+ · Low tech
(rendered from ref tool meta filter values — read only here)
──────────────────────────────────────────────
[ Start tool → ] (disabled until at least one group has ≥1 named member)
When a step is edited (title or body changed from the original):
- Record the variation in meta.local_variations
- Show a small “modified” indicator next to that step
- A “reset to original” link restores the reference text
“Start tool →” button:
- Sets tool_instance meta.status = “active”, meta.started_at = now
- Saves via PUT /api/nodes/:id
- Switches to Groups tab
- Steps become read-only
Groups tab
A grid of group cards — one per group_instance child node.
GROUPS 3 [+ Add group]
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ GROUP 1 │ │ GROUP 2 │ │ GROUP 3 📄 │
│ ● digital │ │ ● digital │ │ ○ paper │
│ │ │ │ │ │
│ Tom P mapper │ │ [empty] │ │ [empty] │
│ Sarah K noter │ │ │ │ │
│ Amara D walker │ │ │ │ │
│ │ │ │ │ │
│ not started │ │ not started │ │ not started │
│ [Edit] │ │ [Edit] │ │ [Upload] [Edit] │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Group card states:
not_started→ dim borderin_progress→ accent border, step progress indicatorsubmitted→ green border, “✓ submitted”paper_pending→ amber border, “📄 awaiting upload”
Modality toggle: Each card has a small toggle: ● digital / ○ paper.
Switching to paper shows “📄” badge and adds Upload button.
Available before and during the session (groups can switch if device fails).
Edit group (inline panel below the card):
Opens inline when Edit is clicked:
Group 1 ● digital ○ paper
Members:
Name: [Tom P ] Role: [mapper ] [×]
Name: [Sarah K ] Role: [noter ] [×]
Name: [Amara D ] Role: [walker ] [×]
Name: [ ] Role: [ ] [+ add member]
Roles suggested by this tool: mapper, noter, walker, interviewer
(pulled from ref tool node meta.suggested_roles if it exists)
[Save] [Cancel]
Names and roles are free text. No validation — blanks acceptable, duplicates acceptable. Save writes to group_instance meta.members via PUT /api/nodes/:id.
Upload button (paper groups):
Opens the image upload flow (existing image attachment feature) pre-configured for this group instance. On upload completion:
- Sets group_instance meta.uploaded_by = current user id
- Sets group_instance meta.on_behalf_of = group_instance_id
- Sets group_instance meta.status = “submitted”
- Creates a contribution node as child of the group_instance
+ Add group button: Creates a new group_instance node as child of this tool_instance. Auto-numbers (Group 4, Group 5 etc).
Contributions tab
All contribution nodes across all group instances for this tool, displayed as cards.
CONTRIBUTIONS 7 Filter: [ All ] [ Group 1 ] [ Group 2 ] ...
[contribution card — group name, timestamp, approval status, content preview]
[contribution card]
...
[ + Add contribution ] (facilitator can add directly — proxy for paper groups)
Matches the contributions area from Phase 3 but scoped to this tool instance. Group filter tabs pull group names from group_instance nodes.
“+ Add contribution” opens quick-capture pre-filled:
- type: artefact
- parent: the group_instance_id (not the tool_instance directly)
- meta.tool_instance_id, meta.phase, meta.movement inherited
- meta.approval_status: pending
- meta.uploaded_by: current user (for proxy tracking)
Summary tab
Read-only. Available at all times but most useful after completion.
SUMMARY
Status: Active · Started: [datetime]
GROUPS
Group 1 ● Tom P · Sarah K · Amara D submitted 3 contributions
Group 2 ● [unnamed] in progress step 2/5
Group 3 📄 [unnamed] paper pending 0 uploaded
ACTIVITY TRACE (Group 2)
Step 1 entered 10:23 exited 10:31 (8 min)
Step 2 entered 10:31 in progress
⚠ Group 3 has not submitted. Paper groups need facilitator upload.
⚠ Group 2 has been on step 2 for 12 minutes.
CONTRIBUTIONS 7 total · 5 pending approval · 2 approved
The ⚠ warnings are the facilitator’s nudge system:
- Paper group not yet uploaded → always show if session is active
- Group on same step for > 10 minutes → show (time threshold configurable in tool_instance meta, default 10 min)
- Group submitted with 0 contributions → show
These are computed client-side from step_trace timestamps and contribution counts — no new endpoint needed.
What does NOT change
- Standard node view — unchanged, accessible via “node view ↗” link
- Reference tool nodes — unchanged (read from, not written to, by instances)
- Project dashboard — unchanged (progress grid already reads contribution counts)
- Auth system — unchanged
- Image map feature — unchanged (used by Upload button for paper groups)
- Quick capture overlay — unchanged (used as base for contribution capture)
- Tree navigation — unchanged (tool_instance and group_instance nodes appear in tree as children of phase nodes and tool_instances respectively)
File changes
db/database.js — filter_options table + seed
api/filter-options.js — GET and POST endpoints
api/tool-instances.js — POST endpoint (creates instance + group instances)
api/projects.js — add /api/projects/:id/progress update for tool instances
server.js — mount new routers
lfti-spine.html — tool instance view, group management,
filter pill extensibility, boot fetches filter options
When you’re done — test in order
-
Piece 1: As institution_admin, add “Playing fields” as a new location filter option via the tool browser. It appears as a filter pill immediately. As student, the add link is not visible.
-
Piece 2: Select Body Map and Story Circle on Past — Gather. Click “Start this phase →”. Two tool_instance nodes appear in the tree as children of Past — Gather. Each has steps parsed from the reference tool body and a references relation to the correct ref tool node.
-
Piece 3: Expand a tool_instance node. See 3 group_instance child nodes (or matching suggested_groups count). Each has modality: digital, status: not_started.
-
Piece 4 — Setup tab: Click a tool_instance node. See the dashboard view with Setup tab active. Steps are editable. Edit one step body — see “modified” indicator and variation recorded in meta.local_variations. Click “reset to original” — step body restored.
-
Piece 4 — Groups tab: Switch to Groups tab. See 3 group cards. Edit Group 1 — add 3 member names and roles, save. Toggle Group 3 to paper — Upload button appears. Click “+ Add group” — Group 4 appears.
-
Piece 4 — Start: Click “Start tool →” (after adding at least one named member). Status changes to Active. Setup tab steps become read-only. Summary tab shows Group 1 with members listed.
-
Piece 4 — Contributions: Switch to Contributions tab. Click “+ Add contribution”. Quick-capture opens. Save a test contribution. It appears in the contributions tab with “pending” badge and the correct group name. Summary tab shows updated contribution count and ⚠ for groups with 0 contributions.