The Meta Level

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-options and store in state.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 option link 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 border
  • in_progress → accent border, step progress indicator
  • submitted → 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.


← All notes · More from LFTI