13 KiB
Rank
Interactive feature prioritization tool for rank.friborg.uk.
Product definition
Rank is a fast intake and prioritization board for product ideas from Jimmi and agents.
Core loop:
Capture idea → score impact/effort/confidence/urgency → drag into milestone → revisit top-ranked work
Chosen subdomain: rank.friborg.uk — short, memorable, and honest about the job.
UX principles
- One-screen capture, no modal ceremony.
- Keyboard-first:
/focuses capture, Enter saves. - Plain sharp visual system: zero rounded corners, dark space/glass, high contrast.
- Milestones are customizable lanes, not a rigid roadmap prison.
- Agents can post ideas through the same API endpoint as the UI.
Architecture
- Node/Express app on port
3045 - Static SPA in
public/ - Appwrite TablesDB persistence
- Docker deploy on Unraid
- Gitea remote repo
- Nginx Proxy Manager routes
rank.friborg.uk→192.168.30.100:3045
Appwrite schema
Database: priority_rank
Tables:
ideas— title, description, source, sourceName, milestoneId, impact, effort, confidence, urgency, score, rank, labels, notes, archivedmilestones— name, description, horizon, color, position, activeactivity— small append-only UX feed
Scattermind → Ranker bridge
Ranker's continuation job is narrow:
Snapshot / Concept Map → candidate feature/action set → Rank-ready build order
POST /api/rank-feedback accepts a prioritix-feature-set-v1-style payload from Scattermind and returns ranked items plus buildOrder.doFirst / validateNext / defer / park. It accepts candidate arrays as features, actions, nextMoves, candidates, experiments, validationTests, or proofTests either at the top level or under featureSet, and it can consume nested Snapshot or Concept Map artifacts directly, so Scattermind can hand off snapshot.nextActions / nextMoves / recommendedActions / suggestedActions or conceptMap.nextActions / nextMoves / recommendedActions / suggestedActions without renaming them into fake software features. Experiment/proof-test arrays default into Validate next candidates, preserving source provenance while keeping them out of the active build-first lane. Sectioned Concept Maps may also include validateNext, deferred, and parkingLot; Ranker combines those sections into one build-order pass while preserving sourceSection and treating deferred/parked sections as lane hints. Ranker also accepts an already-laned Build Order object at top level, under featureSet, or as conceptMap.buildOrder (doFirst / validateNext / deferred / parkingLot, with aliases like buildNow, testManually, and probablyNoise). String items in those lanes are normalized into candidate actions, while object items can use move, questionToAnswer, or evidenceQuestion aliases. Normalized candidate objects may also use lane as a lane hint (do-first, validate-next, defer, park) without overwriting sourceSection, so Scattermind exports that separate recommendation from provenance remain traceable. Empty wrapper arrays are ignored rather than allowed to shadow a real nested Concept Map action set, and non-empty normalized wrappers are merged with Concept Map validation/deferred/parking sections rather than dropping that context. That keeps partially-normalized Scattermind exports rankable without losing the source lane contract. It also returns a brief with source, next-48-hour actions, carried-forward assumptions, source trace, and what would change the ranking, plus handoff.copyableText: a plain-text Build Order handoff with Do first / Validate next / Defer / Park, readiness, carried context, source trace, and the explicit rule that only the Do first item is active. This gives Scattermind (or a tired copy/paste user) a stable artifact to save without reconstructing the decision from JSON fields. Lane-level buildOrderDetails still carry each item's reason, next step, evidence question, success/kill signals, source section, lane source, score, and confidence, while the handoff object (rank-feedback-result-v1) carries source provenance, item trace rows, explicit next-step/success/kill signals when Scattermind provides them, copyable text, and contract warnings for missing artifact IDs, source sections, original prompt provenance, or evidence on active items. If Scattermind sends the current paid Concept Map shape as lenses rather than arrays, Ranker can parse conceptMap.lenses.channel.content / buildOrder labels (Build first, Test manually, Defer, Probably noise) into rank-ready candidates and can read guardrails from the risk lens. Keep this contract action-first; do not use it as a reason to add generic dashboard, auth, billing, or workspace layers before the bridge has proof.
Candidate items may include optional 1–10 rankerHints (value, effort, confidence, urgency, revenue, novelty, risk). Ranker blends those explicit Scattermind hints with text heuristics; effort is inverted into feasibility. Hints improve the defended order, but recommendedLane: "defer" or "park" remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send sourceSection and evidenceNeeded on each active next move. Scattermind can also send targetAudience, constraints, assumptions, and nonGoals / avoid at the top level, in featureSet, inside snapshot.context or conceptMap.context, or as a structured top-level context object with summary, targetAudience, constraints, nonGoals / avoid, and assumptions; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper artifact guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking [object Object]. If Scattermind only has a flat context string or schema-light structured context summaries (context.summary, snapshot.context.summary, conceptMap.context.summary, etc.), Ranker now extracts simple guardrails such as Solo builder, Manual proof, Avoid ..., No ..., Non-goal: ..., Not yet ..., and Do not ... into input.decisionContext / handoff.decisionContext so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in input.decisionContext and handoff.decisionContext, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (preview-2), and reports the normalization in handoff.warnings / handoff.itemTrace so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level reference_code, working_name, ideaText, and string-valued lenses.channel / lenses.risk fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first.
Candidate trace note: candidate-level sourceItemId / traceId, sourceTitle / lensTitle, and sourceExcerpt / sourceQuote are preserved in ranked items, buildOrderDetails, and handoff.itemTrace. Lens-only Build Order text is also split into deterministic concept-map.lenses.channel#N source IDs with the original labelled sentence carried as sourceQuote, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as concept-map.buildOrder.validateNext#1 and carry the original string as sourceQuote, so simple Scattermind exports stay addressable downstream instead of becoming anonymous feature-1 rows. Ranker also accepts the current Scattermind storage-row shape with referenceCode, ideaText, context, and string-valued fullReadingJson / full_reading_json; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. The decision brief.quickGlance.sourceTrace now repeats the winning item's source section/id/title/quote, and both brief.source.originalPromptExcerpt / handoff.source.originalPromptExcerpt or, when the original prompt is unavailable, sourceSummaryExcerpt carry the source context so a downstream Scattermind handoff can show why the build order exists without digging through input.provenance. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from.
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say Continue first, Make tangible, Try next, Evidence next, Hold for later, or Set aside; Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as continueFirst, evidenceNext, holdForLater, and setAside. Ranker maps those to doFirst / validateNext / defer / park while preserving the softer original label in sourceQuote or candidate source trace.
Lane safety note: explicit Scattermind defer / park hints are hard rails, not mild suggestions. Source nonGoals / avoid guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; the result will mark the lane source as source-non-goal so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff source.requiresSourceTrace is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff readiness now gives downstream bridge consumers a deterministic gate: ready, usable-with-warnings, needs-source-context, or blocked, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. Handoff activeSlice (ranker-active-slice-v1) is the compact machine-readable continuation unit: one active item, its proof/evidence/success/kill signals, source anchor, held-back items, readiness status, and the rule that only this slice is build-ready. For tired first-screen users, brief.decisionReceipt repeats the one active move, first proof step, evidence question, held-back items, source anchor, and the handoff rule that only Do first is active; use it as the compact result strip before showing the full lane board. For low-friction handoff, /api/rank-feedback also detects a raw Scattermind/Concept Map JSON object pasted into idea, ideaText, optionsText, or wrapper keys such as payload; it expands that object before ranking and reports input.embeddedPayloadSource so the public form can accept copy/paste exports without a custom import screen. If a Concept Map only carries questions_to_sit_with / questionsToSitWith / openQuestions and no explicit build-order lanes or action threads, Ranker converts those questions into Validate-next evidence actions with source trace instead of pretending they are software features.
Recommended payload shape:
{
"schema": "prioritix-feature-set-v1",
"sourceName": "Scattermind",
"artifactId": "snapshot_or_concept_map_id",
"snapshotTitle": "Plain idea title",
"conceptMapId": "optional_concept_map_id",
"originalPrompt": "The user's starting prompt, trimmed for provenance",
"idea": "What Scattermind clarified",
"context": "Important constraints: solo builder, non-AI-native user, avoid dashboard swamp, etc.",
"mode": "mvp",
"featureSet": {
"features": [
{
"id": "build-order-preview",
"title": "Build order preview",
"description": "Show do first, validate next, defer, and park with reasons.",
"userValue": "A tired builder sees the next move without opening a dashboard.",
"evidenceNeeded": "Can 3 non-AI-native users understand the first recommended action?",
"proofSteps": ["Show a static result screen to 3 people"],
"rankerHints": {
"value": 8,
"effort": 3,
"confidence": 7,
"urgency": 6,
"revenue": 4,
"novelty": 5,
"risk": 3
},
"dependencies": [],
"risk": "May become generic roadmap UI if the source context is lost.",
"recommendedLane": "validate-next",
"sourceSection": "concept-map.nextMoves"
}
]
}
}
Commands
npm run setup:appwrite
npm run check
PORT=3045 node server.js
npm run smoke
Agent idea post:
curl -X POST https://rank.friborg.uk/api/ideas \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $RANK_AGENT_TOKEN" \
-d '{"title":"Add public roadmap export","source":"agent","sourceName":"Rook","impact":8,"effort":3,"confidence":7,"urgency":5}'