{
  "version": 1,
  "updated": "2026-10-27",
  "categories": [
    {
      "id": "accounts",
      "name": "Sign-in and accounts"
    },
    {
      "id": "safety",
      "name": "Safety and trust"
    },
    {
      "id": "payments",
      "name": "Payments and wallet"
    },
    {
      "id": "community",
      "name": "Social and community"
    },
    {
      "id": "dating",
      "name": "Dating and matching"
    },
    {
      "id": "testing",
      "name": "Testing"
    },
    {
      "id": "growth",
      "name": "Growth and sharing"
    },
    {
      "id": "ai",
      "name": "AI"
    },
    {
      "id": "maps",
      "name": "Maps and location"
    },
    {
      "id": "business",
      "name": "Business tools"
    },
    {
      "id": "app-basics",
      "name": "App basics"
    },
    {
      "id": "staff",
      "name": "Staff and admin"
    }
  ],
  "blocks": [
    {
      "id": "app-update-banner",
      "name": "App update banner",
      "summary": "Shows \"⚠️ Your app is out of date\" with 🔄 Update app and ❓ How to update when a newer web build is online, and updates by itself only when safe (idle, nothing typed, no pop-up, form or payment open), once per build.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "updates",
        "version",
        "banner",
        "reload",
        "pwa",
        "web"
      ],
      "fit": "Any web app (plain JS, React or Expo / React Native Web) whose users keep old copies open and need to move to the newest version.",
      "notFor": "Phone store apps (native builds update through the stores) or sites that never change.",
      "platforms": [
        "web",
        "react",
        "expo",
        "react-native"
      ],
      "stack": [
        "javascript",
        "typescript",
        "react",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "app name + site address (fills the words and how-to steps)",
        "own words and how-to steps",
        "current build id + version file address (version.json or index.html)",
        "check interval (5 minutes) and first check (3 s)",
        "idle time before an auto-update (60 s)",
        "auto-update on/off",
        "paths that never auto-update (payment pages)",
        "the app's own 'not now' check",
        "a step before the reload (e.g. stop the database)",
        "optional instant go-live signal",
        "service worker: update or remove"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master with three variants on one shared core (update-core.js): plain JS (no build step), React web (with a Vite build-id plugin) and Expo / React Native Web. All words, steps and hooks are passed in settings; no app names. The host must serve version.json with no-store (and pages no-cache). Labora, Find My Buddy and App Monitor still run their older copies until each app swaps to the master.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/update-banner/"
        ]
      },
      "tests": [
        "functions/test/update-banner.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "usedBy": [
        "labora",
        "find-my-buddy",
        "app-monitor",
        "launchyard"
      ]
    },
    {
      "id": "activities",
      "name": "Activities (post and join)",
      "summary": "Post, show and join local activities: activity types with emoji, cards, a details card with Join / Ask to join / Invite only, places left and who-can-come rules.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "community",
      "tags": [
        "activities",
        "events",
        "meetups",
        "join",
        "social",
        "community",
        "uk"
      ],
      "fit": "A social or community app where people post meet-ups or events and others join them.",
      "notFor": "Paid ticketing or bookings with payments (there is no payment here), or one-to-one appointments.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "activity types and emoji",
        "join types (open / host approves / invite only)",
        "places cap (50)",
        "who can come (anyone / women / men, ID-verified only)"
      ],
      "customisable": "medium",
      "customiseNotes": "Own folder and README, but Find My Buddy look, Surrey demo data and the 171-type pool are baked in. Real saving is not built yet (demo writes go to an on-device pretend database).",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/activities/"
        ]
      },
      "tests": [
        "src/blocks/activities/typePool.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "tester-kit-hook",
      "name": "Tester kit hook",
      "summary": "Loads the Testers Walkthrough tester-cards bubble only on a tester link, so testers can tick cards and report problems inside the app; normal users load nothing.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "testing",
      "tags": [
        "testing",
        "testers",
        "qa",
        "feedback",
        "bug-reports"
      ],
      "fit": "An app being tested with Testers Walkthrough that wants the tester cards to appear on tester links only.",
      "notFor": "Apps not using Testers Walkthrough, or production analytics.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native",
        "firebase-js-sdk"
      ],
      "needs": [
        "testers-walkthrough-project"
      ],
      "settings": [
        "project id",
        "app name",
        "card list file (test-cards.json)"
      ],
      "customisable": "medium",
      "customiseNotes": "Small, but the project id and app name are constants in TestCardsBoot.js, and the app's bundler must give it the real Firebase SDK (FMB maps firebase/* to demo shims). Web only today.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/testcards/TestCardsBoot.js",
          "src/blocks/testcards/toolkitConfig.js"
        ]
      },
      "tests": "none",
      "sellable": false,
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "demo-sandbox",
      "name": "Demo sandbox + network guard",
      "summary": "A Developer preview / demo mode that runs the whole app on an on-device pretend database with seeded people and bots, and a network guard that blocks every request except an allow list (sign-in, reports, the tester kit).",
      "app": "find-my-buddy",
      "status": "built",
      "category": "testing",
      "tags": [
        "demo",
        "sandbox",
        "preview",
        "testing",
        "safety",
        "network",
        "offline"
      ],
      "fit": "An app that wants a safe try-it demo or tester preview where nothing can reach the real server, payments or emails.",
      "notFor": "A simple marketing demo or a static mock-up; it is large and closely tied to the app's own calls.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native",
        "firebase-js-sdk"
      ],
      "needs": [],
      "settings": [
        "allowed hosts and calls (allow list)",
        "blocked hosts list",
        "seed data"
      ],
      "customisable": "low",
      "customiseNotes": "Around 60 files carried over from Labora: many are Labora-specific (jobs, money, diary, accountant calls) and the app's bundler maps firebase/* to these shims. The network guard (networkGuard.js) is the most reusable part.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/sandbox/",
          "src/sandbox/networkGuard.js"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "auth-18-plus",
      "name": "Sign-in / sign-up 18+",
      "summary": "Email sign-in and sign-up for one plain member account, asking name, town, an 18+ tick and the Terms tick; the server makes the profile.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "accounts",
      "tags": [
        "auth",
        "sign-in",
        "sign-up",
        "age-check",
        "terms",
        "accounts"
      ],
      "fit": "A consumer app with one member type that needs email sign-up with an 18+ self-declaration and Terms consent.",
      "notFor": "Apps needing real ID checks, social logins or several account roles.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native",
        "firebase-js-sdk"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "start view (log in / sign up)"
      ],
      "customisable": "low",
      "customiseNotes": "Depends on many app files (sign-up service, Terms screen, profanity and town checks, sandbox helpers, welcome block) and a server sign-up function; the support contact and wording are hard-coded.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/auth/FmbAuthScreen.js",
          "src/blocks/auth/authLook.js"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "profiles",
      "name": "Member profiles",
      "summary": "Member profiles with photo, age display choice, town only, interests with levels, and exact free times (a usual week plus one-off dates).",
      "app": "find-my-buddy",
      "status": "built",
      "category": "accounts",
      "tags": [
        "profiles",
        "users",
        "availability",
        "interests",
        "privacy",
        "social"
      ],
      "fit": "A social or matching app where members show interests and when they are free, without sharing an address or contact details.",
      "notFor": "Business or trade profiles with documents, reviews or payments.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "levels",
        "age display (exact / range / hidden)",
        "map visibility (hidden / rough / exact)",
        "quick-pick interests"
      ],
      "customisable": "medium",
      "customiseNotes": "freeTimes.js is pure logic and easy to reuse. The screens carry the Find My Buddy look and demo profiles; saving is on this device only until real saving is built.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/profiles/"
        ]
      },
      "tests": [
        "src/blocks/profiles/freeTimes.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "groups",
      "name": "Groups",
      "summary": "Standing groups with members, join types, a group chat button and repeat activities, plus a create-a-group form.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "community",
      "tags": [
        "groups",
        "community",
        "clubs",
        "members",
        "social"
      ],
      "fit": "A community app where people form clubs or groups that meet again and again.",
      "notFor": "Work teams with permissions, or apps that need real group saving today.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "join type (open / approval / invite)",
        "who can come",
        "ID-verified only"
      ],
      "customisable": "medium",
      "customiseNotes": "Demo only: groups live on the device until reload. Surrey demo groups and Plus/Pro notes are baked in; host approvals, invites and real group chats are not built.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/groups/"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "marketplace",
      "name": "Marketplace",
      "summary": "Free-to-list local marketplace for used gear, offers, tickets and services; buyer and seller arrange payment in chat (no in-app payment).",
      "app": "find-my-buddy",
      "status": "built",
      "category": "community",
      "tags": [
        "marketplace",
        "listings",
        "classifieds",
        "buy-sell",
        "local",
        "uk"
      ],
      "fit": "A community app that wants a simple listings board where people message the seller.",
      "notFor": "Shops needing checkout, payments, stock or delivery.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "typescript",
        "react-native"
      ],
      "needs": [
        "firestore"
      ],
      "settings": [
        "category labels",
        "used-gear sub-categories",
        "free listing period (30 days)",
        "promo banner",
        "coming-soon words",
        "look (theme colours, radius, spacing)",
        "who is a member and the join button"
      ],
      "customisable": "low",
      "customiseNotes": "Master 2.3.1 in app-market blocks/marketplace: settings (categories, sub-categories, services hint, promo, coming-soon words, theme), category helpers, and two display-only screens: the members gate (isMember / onJoin passed in) and the promo banner. The list, detail and post screens are still in Find My Buddy and move into the master as their own change later.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/marketplace/"
        ]
      },
      "tests": [
        "blocks/marketplace/test/marketplace.test.js",
        "blocks/marketplace/test/marketScreens.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "meetup-safety",
      "name": "Meet-up safety",
      "summary": "Stay-safe tools for meeting strangers: share the meet-up with a trusted friend, live location for the meet-up window, check-in and check-out, an \"Are you OK?\" check and a friend view with a missed-check alert.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "safety",
      "tags": [
        "safety",
        "location",
        "trusted-contact",
        "check-in",
        "meetups",
        "uk"
      ],
      "fit": "Any app where people meet in person (activities, dating, buying and selling) and want a friend kept in the loop.",
      "notFor": "Emergency services or lone-worker compliance; it is not a replacement for calling 999.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "\"Are you OK?\" times (1 h / 2 h / 3 h after the end)",
        "live window (30 min before start to end)",
        "safety tips"
      ],
      "customisable": "medium",
      "customiseNotes": "Demo only: no server yet, state stays on the device. The server door (safetyServer.js) and plan (SERVER-PLAN.md) are ready for a scheduled server check; demo contacts and wording are baked in.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/safety/"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "chat-report-block",
      "name": "Chat + report / block",
      "summary": "Inbox and threads (people, activities, groups) with photos, read ticks and a pre-check that stops phone numbers, emails and addresses before people connect; plus a report sheet and block that any screen can open.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "community",
      "tags": [
        "chat",
        "messaging",
        "moderation",
        "report",
        "block",
        "safety",
        "privacy"
      ],
      "fit": "A social or marketplace app that needs member chat with built-in reporting, blocking and contact-detail protection.",
      "notFor": "Real-time support desks or apps that need the messages saved on a server today.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "report reasons",
        "contact-detail blocking (before connecting, always in groups)",
        "aliases before connecting"
      ],
      "customisable": "medium",
      "customiseNotes": "contactDetect.js and chatText.js are pure and easy to reuse. The screens and store are demo only (on-device), with Find My Buddy look and demo chats.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/chat/",
          "src/blocks/chat/report/"
        ]
      },
      "tests": [
        "src/blocks/chat/contactDetect.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "welcome-steps",
      "name": "Welcome steps",
      "summary": "Three quick first-time screens (interests, town, free times) and then a list of matching activities.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "accounts",
      "tags": [
        "onboarding",
        "welcome",
        "matching",
        "interests"
      ],
      "fit": "An app that wants new members to give a few answers and see matching things straight away.",
      "notFor": "Long sign-up forms, KYC or business onboarding.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "when it shows (once after sign-up, or on request)"
      ],
      "customisable": "medium",
      "customiseNotes": "Uses the profiles and activities blocks; matching rules (12 miles, free-time overlap) are in welcomeMatch.js. Saving is on this device only.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/welcome/"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "map-filters",
      "name": "Map filters",
      "summary": "A filters pop-up for a map and list of activities: town or postcode, distance, type, level, day and time, ID-verified hosts only, and women / men only.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "maps",
      "tags": [
        "maps",
        "filters",
        "search",
        "location",
        "uk"
      ],
      "fit": "An app that shows local items on a map or list and needs simple filters.",
      "notFor": "Full-text search or apps without locations.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "maps"
      ],
      "settings": [
        "distance choices (5 / 10 / 20 miles, all UK)",
        "levels",
        "day and time choices"
      ],
      "customisable": "medium",
      "customiseNotes": "filterLogic.js is pure. The panel is wired into the app's map screen (UnifiedJobsScreen.js) and reads fixed activity field names.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/map-filters/"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "reliability-score",
      "name": "Reliability score",
      "summary": "Shows \"Turned up 9 of 10\" on profiles, lets hosts mark no-shows, and pauses joining for 7 days after 3 no-shows in 30 days.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "safety",
      "tags": [
        "reliability",
        "no-show",
        "trust",
        "reputation",
        "moderation"
      ],
      "fit": "Any app with sign-ups to events or meet-ups where no-shows are a problem.",
      "notFor": "Star ratings or written reviews.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "no-show rule (3 in 30 days)",
        "pause length (7 days)",
        "where it saves (storage)",
        "look (theme colours, radius, spacing)",
        "all words (ready for translation)",
        "row tap and own row drawing"
      ],
      "customisable": "high",
      "customiseNotes": "Master 2.3.1 in app-market blocks/reliability-score: rules, store (storage passed in, in memory by default, optional live record), score badge and reliability screen. Screens use plain React Native StyleSheet; colours, radius and spacing come from the app's theme setting and every text from its words setting. The host's no-show list (NoShowList) is in the master; no server copy or appeal route yet.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/reliability-score/"
        ]
      },
      "tests": [
        "blocks/reliability-score/test/reliability.test.js",
        "blocks/reliability-score/test/reliabilityStore.test.js",
        "blocks/reliability-score/test/reliabilityScreens.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "kit-list",
      "name": "Equipment checklist",
      "summary": "What-to-bring lists: personal lists, a list on each activity, and who's-bringing-what on group lists, with ticks, claims, suggestions and share by WhatsApp, copy or print.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "community",
      "tags": [
        "checklist",
        "lists",
        "shopping-list",
        "sharing",
        "groups"
      ],
      "fit": "An app where people need a shared or personal checklist tied to an event or group.",
      "notFor": "Stock control, orders or task management with deadlines.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "list modes (tick / claim / view)",
        "item limit (60)",
        "suggestions per activity type"
      ],
      "customisable": "medium",
      "customiseNotes": "kitLogic.js is pure CommonJS and easy to reuse. Screens copy the Labora shopping-list look; lists are on the device only (demo).",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/kit-list/"
        ]
      },
      "tests": [
        "src/blocks/kit-list/kitLogic.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "testers",
      "name": "Testers",
      "summary": "Tester profiles (devices, interests, area), test requests from developers, join / finish / accept, points and badges, and reporting a tester to staff. How many tests a tester may have at once comes from the Tester pool block.",
      "app": "launchyard",
      "status": "built",
      "category": "testing",
      "tags": [
        "testing",
        "testers",
        "qa",
        "points",
        "badges",
        "community",
        "moderation"
      ],
      "fit": "A platform that matches app developers with people who test their apps and rewards the testers.",
      "notFor": "Automated testing or a single team's internal QA.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "testers.js holds pure helpers (devices, badge levels as constants); the callable functions (saveTesterProfile, createTestRequest, joinTest, finishTest, acceptTest, reportTester and admin ones) live in the big functions/index.js with collection names and wording hard-coded. Pages: public/tester.html, tester.js, testers-ui.js.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/testers.js",
          "functions/index.js",
          "public/tester.js",
          "public/testers-ui.js"
        ]
      },
      "tests": [
        "functions/test/testers.test.js",
        "functions/test/testers.emulator.js",
        "functions/test/testerreports.emulator.js",
        "functions/test/testflow.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "tester-pool",
      "name": "Tester pool",
      "summary": "One shared pool of testers for every app asking for testers: a list ordered paid first, then newest, then the fewest testers so far; device and category filters; one tap joins and opens the test; how many tests each tester may have at once follows supply; the owner's own apps get unlimited free testers.",
      "app": "launchyard",
      "status": "built",
      "category": "testing",
      "tags": [
        "testing",
        "testers",
        "qa",
        "marketplace",
        "sharing-out",
        "community"
      ],
      "fit": "A platform where many developers ask a shared group of people to test their apps and testers should be spread fairly across them.",
      "notFor": "A single team testing its own app, or automated testing.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "tokens per accepted test",
        "token price per tester",
        "tests on the go: least and most (1 and 5)",
        "newest group in hours (24)",
        "most testers for an own app",
        "own-apps allow-list (listing ids) and owner accounts",
        "tokens wording"
      ],
      "customisable": "medium",
      "customiseNotes": "Pure helpers in tester-pool.js (settings, own apps, sharing-out limit, list order + filters); settings in app-config/tester-pool.json with staff changes saved per test/live in poolConfig. The callables (testerPool, myPoolInfo, adminPool*) and the join / ask wiring still live in functions/index.js next to the Testers block. Uses the Tokens block for paying per tester.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/tester-pool.js",
          "functions/app-config/tester-pool.json",
          "functions/index.js",
          "public/tester.js",
          "public/testers-ui.js"
        ]
      },
      "tests": [
        "functions/test/tester-pool.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "tokens",
      "name": "Tokens (no cash value)",
      "summary": "A server-only token balance with a full history: earned for work done (e.g. an accepted test), held when something is asked for, spent as it is used, and returned when it is not. Never cash, never transferable, never refunded as money; members only read their own.",
      "app": "launchyard",
      "status": "built",
      "category": "payments",
      "tags": [
        "tokens",
        "credits",
        "rewards",
        "ledger",
        "non-cash",
        "wallet"
      ],
      "fit": "An app that rewards members with a spend-only credit they can use inside the app (or a family of apps), with a clear balance and history.",
      "notFor": "Real money, gift cards or anything that can be cashed out or sent to someone else (use a wallet with a payment provider).",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "account and ledger collection names",
        "separate test and live balances (<uid>~test)"
      ],
      "customisable": "high",
      "customiseNotes": "tokens.js: pure moves (earn, hold, spend, unspend, return, placeback, give, take) that never go below zero, plus a small store used inside a Firestore transaction (read first, then move). Rules: read own account + ledger lines, no client writes. Later it moves into the global wallet as a separate non-cash token balance.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/tokens.js",
          "firestore.rules",
          "functions/index.js",
          "public/tester.js"
        ]
      },
      "tests": [
        "functions/test/tester-pool.test.js",
        "functions/test/tokens-rules.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "test-swaps",
      "name": "Test swaps",
      "summary": "\"I test yours, you test mine\": two developers swap tests, both get 7 days, and the one who does not finish is flagged (loses swap points, a mark on their score).",
      "app": "launchyard",
      "status": "planned",
      "category": "testing",
      "tags": [
        "testing",
        "testers",
        "swaps",
        "community",
        "fairness"
      ],
      "fit": "A developer community where members test each other's apps instead of paying.",
      "notFor": "Paid testing or a single team's internal QA.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "days to finish (7)",
        "swap points lost when not finished"
      ],
      "customisable": "medium",
      "customiseNotes": "Planned (docs/plan-tester-pool.md, NEXT). Builds on the Tester pool and Testers blocks.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/plan-tester-pool.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "boosts",
      "name": "Boosts",
      "summary": "Paid boosts that lift a listing (and its place in the tester list) or a tester-only boost, behind an on/off switch with example prices, payable with tokens too; staff control prices, which apps, and can end a boost early.",
      "app": "launchyard",
      "status": "planned",
      "category": "growth",
      "tags": [
        "boost",
        "promotion",
        "listing",
        "testers",
        "tokens"
      ],
      "fit": "A marketplace that lets makers pay to be seen first for a while, with staff control.",
      "notFor": "Apps without a public list to lift.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "on/off switch",
        "prices (money and tokens)",
        "which apps",
        "how long a boost lasts"
      ],
      "customisable": "medium",
      "customiseNotes": "Planned (docs/plan-tester-pool.md, NEXT). Uses the Tokens block to pay with tokens; money once Stripe is on.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/plan-tester-pool.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "promo-badge-box",
      "name": "Listing badge + rotating \"find more apps\" box",
      "summary": "A \"Listed on AppQuay\" badge and a rotating box of other listings that developers put on their own sites; listings that show it get more turns, and signed-in visitors can pick interests.",
      "app": "launchyard",
      "status": "built",
      "category": "growth",
      "tags": [
        "promotion",
        "marketing",
        "badge",
        "cross-promotion",
        "discovery",
        "interests"
      ],
      "fit": "A directory or marketplace that wants its listings to promote each other.",
      "notFor": "Paid advertising networks or cross-site tracking (paid Sponsored places are not built).",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "Being built. promo.js has pure helpers (box size 3, boost weight 3 as constants); saveMyInterests, promoBox and checkBadge live in functions/index.js. Pages: public/box.html, box.js, box.css, promo-ui.js.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/promo.js",
          "functions/index.js",
          "public/box.js",
          "public/promo-ui.js"
        ]
      },
      "tests": [
        "functions/test/promo.test.js",
        "functions/test/promo.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "connect-own-ai",
      "name": "Connect my own AI",
      "summary": "Planned: lets a person link their own AI assistant (for example ChatGPT or Claude) with a pairing code, so it can read tester reports and mark them done, with on/off switches and a log.",
      "app": "launchyard",
      "status": "planned",
      "category": "ai",
      "tags": [
        "ai",
        "mcp",
        "oauth",
        "integration",
        "testing",
        "automation"
      ],
      "fit": "An app that wants users' own AI assistants to act on their data safely, without the app paying for the AI.",
      "notFor": "Apps that want to run AI on their own server only.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Not built yet. Modelled on Labora's connector (OAuth + PKCE + MCP server in one HTTPS function). Rating set when built.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/plan-ai-and-wallet.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "app-wallet",
      "name": "Wallet (one per app)",
      "summary": "A prepaid, spend-only wallet inside one app: card top-ups (Stripe Checkout, £5-£200), optional auto top-up from a saved card (the person sets the amount, the level and a monthly limit; switched off after a failed payment, never retried), a monthly allowance for subscription tiers that is used first and resets each month, charges for AI work at 1.5x cost with the price shown first, an append-only ledger and staff refunds of unused top-ups. Each app keeps its own balance.",
      "app": "launchyard",
      "status": "built",
      "category": "payments",
      "tags": [
        "payments",
        "wallet",
        "credits",
        "billing",
        "stripe",
        "allowance",
        "uk",
        "auto-top-up",
        "saved-card"
      ],
      "fit": "An app that charges small amounts for usage (for example AI help) from a prepaid balance.",
      "notFor": "Moving money between people, cash-outs or withdrawals, or one balance shared across several apps (each of those brings UK e-money rules).",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "stripe"
      ],
      "settings": [
        "top-up range (£5-£200)",
        "markup on AI cost (1.5x)",
        "time zone for the allowance and auto top-up month (Europe/London)",
        "monthly allowance per tier (setAllowance)",
        "allowed return origins for Stripe Checkout",
        "auto top-up limits: level £1-£100, monthly limit £20-£500",
        "auto top-up lock timeout (6 hours)",
        "email wording for auto top-up done / failed (emails.js)"
      ],
      "customisable": "high",
      "customiseNotes": "The maths is one pure file (functions/wallet.js, settings as constants at the top) and the Firestore side is one file that takes db as a parameter (functions/walletStore.js). The seven Cloud Functions (walletGet, walletTopUpStart, walletStripeWebhook, adminWalletRefund, walletSaveCardStart, walletRemoveCard, walletAutoTopUpSave) are a short section of AppQuay functions/index.js that must be copied out and wired to the other app's own staff check, sign-in helpers and return origins. The page panel is in AppQuay account.html/account.js. Needs the app's own Stripe account and two secrets. Stripe card refunds are done in the Stripe dashboard for now. Auto top-up needs the webhook events payment_intent.succeeded and payment_intent.payment_failed, and every function that spends from the wallet must load the Stripe secret key.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/wallet.js",
          "functions/walletStore.js",
          "functions/index.js (Wallet section)",
          "firestore.rules (wallets, walletLedger)",
          "firestore.indexes.json (walletLedger uid + atMs)",
          "public/account.html",
          "public/account.js",
          "docs/wallet-block.md",
          "functions/emails.js (walletAutoTopUpDone, walletAutoTopUpFailed)"
        ]
      },
      "tests": [
        "functions/test/wallet.test.js",
        "functions/test/wallet.emulator.js",
        "functions/test/walletauto.emulator.js",
        "functions/test/emails.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "block-shop",
      "name": "Block shop (subscription tiers with block picks)",
      "summary": "Sells code blocks as subscription tiers paid monthly or yearly, each with a number of block picks. Each pick is a 12-month licence for one block in one app; warnings 14 and 3 days before the end, then \"renew or buy forever\". A forever licence is one block, one app, one payment. Developers' blocks are paid from a monthly pool (50% of the developers' part of subscription money, split by use) and 50% of a forever sale. Server switch (off = no payment call) and page switch (prices hidden).",
      "app": "launchyard",
      "status": "built",
      "category": "payments",
      "tags": [
        "payments",
        "subscriptions",
        "licences",
        "stripe",
        "marketplace",
        "makers",
        "revenue-share",
        "uk"
      ],
      "fit": "An app that sells reusable code, templates or plugins by subscription with a set number of picks, with outside developers paid a share.",
      "notFor": "Paying developers out (needs Stripe Connect, not built), physical goods, or one-off downloads with no licence dates.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "cloud-scheduler",
        "stripe"
      ],
      "settings": [
        "tiers (name, monthly price in pence, picks, apps)",
        "yearly price in months",
        "extra app price and most extra apps",
        "forever licence price",
        "licence length in months (12)",
        "warning days (14 and 3)",
        "developers' share (0.5)",
        "which block statuses can be picked",
        "test plans on the test address on/off",
        "warning emails on/off",
        "collection names",
        "every page and server message",
        "prices shown on/off (page switch)"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/block-shop (no app names); an app copies it with tools/blocks-pull.js (server and page folders), fills ONE settings file (all prices), passes its own block list, return addresses and email sender as hooks, and wraps the calls in its own sign-in and staff checks. The server switch comes from the app's environment. Payouts to developers are not built (statements only, needs Stripe Connect).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/block-shop/",
          "functions/block-shop/",
          "public/block-shop/",
          "functions/app-config/block-shop.json",
          "functions/shop.js",
          "functions/index.js (Block shop section)",
          "public/shop.html",
          "public/shop.js",
          "firestore.rules (shopSubscriptions, shopLicences, shopNotices)",
          "docs/brief-app-monitor-shop.md"
        ]
      },
      "tests": [
        "blocks/block-shop/test/logic.test.js",
        "blocks/block-shop/test/server.test.js",
        "functions/test/block-shop.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "safety-checks",
      "name": "Safety checks for links and uploads",
      "summary": "Checks a submitted link or upload before it goes public: blocks private addresses, Google Safe Browsing, VirusTotal, a Claude review of the page, and picture and copyright checks; each check passes, fails, is skipped or errors.",
      "app": "launchyard",
      "status": "built",
      "category": "safety",
      "tags": [
        "safety",
        "moderation",
        "malware",
        "security",
        "links",
        "uploads",
        "copyright",
        "ai"
      ],
      "fit": "Any app where users submit links, apps, pictures or files that others will open.",
      "notFor": "Checking code you run on your own server, or real-time chat messages.",
      "platforms": [
        "firebase-functions",
        "node"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "safe-browsing-key",
        "virustotal-key",
        "anthropic-key"
      ],
      "settings": [
        "keys passed in (missing key = check skipped)",
        "fetch / DNS / Claude passed in (fakes for tests)"
      ],
      "customisable": "medium",
      "customiseNotes": "checks.js is a separate module with the network passed in. Limits, store hosts, file types and the Claude prompt are constants in the file; the functions that call it (checkApp, recheckApproved) live in functions/index.js.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/checks.js",
          "functions/media.js",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/checks.test.js",
        "functions/test/pictures.test.js",
        "functions/test/media.test.js",
        "functions/test/security.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "ratings-reviews",
      "name": "Ratings + reviews",
      "summary": "Star ratings and reviews that only people who tried the item can leave, screened by Claude and held if they mention rewards; developers can reply and anyone can report a review.",
      "app": "launchyard",
      "status": "built",
      "category": "safety",
      "tags": [
        "reviews",
        "ratings",
        "moderation",
        "trust",
        "uk",
        "ai"
      ],
      "fit": "A catalogue or marketplace that wants trustworthy reviews that follow UK rules on rewarded reviews.",
      "notFor": "Anonymous comment threads or chat.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [
        "wait before a review (hours)"
      ],
      "customisable": "low",
      "customiseNotes": "reviews.js has the pure helpers, but the team app list, reward words, AI model name and wording are hard-coded; the functions (recordTry, submitReview, replyToReview, reportReview, admin ones) live in functions/index.js.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/reviews.js",
          "functions/index.js",
          "public/reviews-ui.js"
        ]
      },
      "tests": [
        "functions/test/reviews.test.js",
        "functions/test/reviews.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "tw-partner-link",
      "name": "Testers Walkthrough partner link-up",
      "summary": "Links a platform to Testers Walkthrough with a partner key: each tester gets their own walkthrough link, card answers come back by signed webhook, and points per card or section turn into Testers Walkthrough credit.",
      "app": "launchyard",
      "status": "built",
      "category": "testing",
      "tags": [
        "testing",
        "testers",
        "integration",
        "webhooks",
        "points",
        "partner"
      ],
      "fit": "A platform that sends testers to Testers Walkthrough and wants per-feature points back.",
      "notFor": "Apps not partnered with Testers Walkthrough.",
      "platforms": [
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "testers-walkthrough-partner-key"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "tw.js has pure helpers (signature check, card list clean-up); the Testers Walkthrough address and earning rules are constants, and the functions (twConnect*, twAccount*, swapPoints, twWebhook) live in functions/index.js. Other partners need Testers Walkthrough terms first.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/tw.js",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/tw.test.js",
        "functions/test/twlink.emulator.js",
        "functions/test/twcredit.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "categories-search",
      "name": "Categories + search",
      "summary": "Fixed categories for each home-page tab (apps, games, music, animation), a server check that a chosen category is valid, and category filters and search on the pages.",
      "app": "launchyard",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "categories",
        "search",
        "filters",
        "catalogue",
        "discovery"
      ],
      "fit": "A catalogue that needs a small fixed category list kept the same on server and page.",
      "notFor": "Large or user-made taxonomies, or full-text search engines.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [],
      "settings": [
        "category lists per section"
      ],
      "customisable": "medium",
      "customiseNotes": "Very small, but the lists are hard-coded twice (functions/categories.js and public/categories.js) and must be kept in step.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/categories.js",
          "public/categories.js"
        ]
      },
      "tests": [
        "functions/test/categories.test.js",
        "functions/test/categories.emulator.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "id": "staff-single-sign-in",
      "name": "One staff sign-in through App Monitor",
      "summary": "Staff sign in once on App Monitor; AppQuay's admin functions check that sign-in and the same staff record server-side, so staff need no separate AppQuay login.",
      "app": "launchyard",
      "status": "built",
      "category": "staff",
      "tags": [
        "auth",
        "staff",
        "admin",
        "single-sign-on",
        "security"
      ],
      "fit": "Mike's own apps that are run from the App Monitor staff hub.",
      "notFor": "Other companies: it trusts one specific staff system.",
      "platforms": [
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "cloud-functions"
      ],
      "settings": [
        "staff project (environment setting)",
        "extra staff account ids (environment setting)"
      ],
      "customisable": "low",
      "customiseNotes": "needAdmin and the staff-record check live in functions/index.js and read fixed staff collection names; internal use only.",
      "location": {
        "repo": "app-market",
        "paths": [
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/staff.emulator.js"
      ],
      "sellable": false,
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24"
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "labora-connect-own-ai",
      "name": "Connect my own AI (Labora)",
      "summary": "Lets a person link their own ChatGPT or Claude to their account once, with 4 on/off permission switches, a demo version and a one-line pointer on screens; the AI then works through a small set of safe tools as that person only.",
      "app": "labora",
      "status": "built",
      "category": "ai",
      "tags": [
        "ai",
        "mcp",
        "oauth",
        "connector",
        "permissions",
        "chatgpt",
        "claude"
      ],
      "fit": "Apps whose users want their own AI assistant to read or help with their account data, without the app paying for the AI.",
      "notFor": "Apps that want to run AI on their own bill, or that need the AI to send messages, pay or delete things.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "the 4 permission switches",
        "which tools the AI may call",
        "demo version on or off"
      ],
      "customisable": "low",
      "customiseNotes": "Server and plan logic are separate files (aiConnector.js, aiConnectorPlan.js), but tool names, wording and Labora's data shapes are written in; another app would need its own tools and screens.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/aiConnector.js",
          "functions/aiConnectorPlan.js",
          "src/components/settings/OwnAiConnectorCard.js",
          "src/components/settings/OwnAiLine.js",
          "src/utils/aiConnector.js",
          "src/utils/ownAiLine.js"
        ]
      },
      "tests": [
        "tests/unit/aiConnectorPlan.test.js",
        "tests/unit/aiConnectorDemoPlan.test.js",
        "tests/unit/aiConnectorScreen.test.js",
        "tests/unit/demoAiConnectorDevice.test.js"
      ]
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "labora-surveys",
      "name": "Surveys",
      "summary": "Short in-app surveys at the right moments (after setup, when leaving, at 90 days, for testers, or custom) shown as a small card, with results for staff.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "surveys",
        "feedback",
        "research",
        "staff",
        "cards"
      ],
      "fit": "Apps that want quick, well-timed feedback from users and a results page for staff.",
      "notFor": "Long forms, anonymous public polls or paid research panels.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "which surveys exist and when they show",
        "custom survey questions"
      ],
      "customisable": "medium",
      "customiseNotes": "Survey plans are data in surveyPlan.js and the card is one component; the staff results page lives in App Monitor's Labora mode.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/surveyPlan.js",
          "functions/surveys.js",
          "src/components/common/SurveyCard.js"
        ]
      },
      "tests": [
        "tests/unit/surveys.test.js",
        "tests/unit/surveyCard.test.js"
      ]
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "popup-keyboard-fix",
      "name": "Pop-up keyboard fix",
      "summary": "Keeps the phone keyboard up while someone types in a pop-up, and makes sure only one pop-up shows at a time.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "ui",
        "keyboard",
        "modal",
        "mobile",
        "forms"
      ],
      "fit": "React Native or Expo apps with forms inside pop-ups where the keyboard closes or jumps.",
      "notFor": "Plain web pages without pop-ups.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "pop-up content and buttons (props)"
      ],
      "customisable": "high",
      "customiseNotes": "One component with props; no Labora data inside.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/components/common/OnePopupModal.js"
        ]
      },
      "tests": [
        "tests/unit/onePopup.test.js"
      ]
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": false,
      "id": "app-monitor-hub",
      "name": "App Monitor hub",
      "summary": "One staff app for all our apps: one sign-in (staffToken) for every app, a switch after sign-in, one job feed with filters by app, and overview tiles per app; each app only adds three small server calls (feed, stats, actions).",
      "app": "app-monitor",
      "status": "built",
      "category": "staff",
      "tags": [
        "admin",
        "staff",
        "dashboard",
        "moderation",
        "sign-in",
        "feed",
        "multi-app"
      ],
      "fit": "A team running several apps that wants one place for staff jobs, approvals and numbers.",
      "notFor": "A single small app with one admin, or end-user dashboards.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "typescript",
        "react",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "which apps appear",
        "per-app feed and stats calls"
      ],
      "customisable": "low",
      "customiseNotes": "Each app mode is its own folder (tw/, launchyard/) but the shell, sign-in and staff roles are tied to our Firebase projects; adding an app means a new mode folder plus its server calls.",
      "location": {
        "repo": "app-monitor",
        "paths": [
          "client/src/lib/twFeed.ts",
          "client/src/lib/lyFeed.ts",
          "client/src/components/ModeShell.tsx",
          "client/src/tw/",
          "client/src/launchyard/"
        ]
      },
      "tests": "none"
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "tw-connect-own-ai",
      "name": "Connect my own AI (Testers Walkthrough)",
      "summary": "Lets a developer's own AI connect to Testers Walkthrough (a terminal handshake with an Approve page, or a password fallback) and use tools to list tester reports, read one with its pictures, and mark it fixed.",
      "app": "testers-walkthrough",
      "status": "built",
      "category": "ai",
      "tags": [
        "ai",
        "connector",
        "testing",
        "bug-reports",
        "mcp",
        "developer-tools"
      ],
      "fit": "Tools where a developer's AI should read and act on reports about their app without the developer copying anything by hand.",
      "notFor": "Apps for people who don't use an AI coding assistant.",
      "platforms": [
        "firebase-functions",
        "node",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "which AI tools are offered"
      ],
      "customisable": "low",
      "customiseNotes": "Handshake and tools are written for Testers Walkthrough's reports and wording.",
      "location": {
        "repo": "testcards-toolkit",
        "paths": [
          "results/plugin/connect.js",
          "server/functions/lib/takeoverHandlers.js",
          "server/functions/lib/connectPlan.js",
          "results/app.js"
        ]
      },
      "tests": [
        "tests/job9-connect.test.js"
      ]
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "tw-partner-server",
      "name": "Partner link-up (Testers Walkthrough side)",
      "summary": "Lets a partner platform make per-tester links for apps whose owners approved it, read the app's card list, and get each answer back as a signed webhook with retries; only link ids are shared, never names or emails.",
      "app": "testers-walkthrough",
      "status": "built",
      "category": "testing",
      "tags": [
        "partners",
        "webhooks",
        "api-keys",
        "testing",
        "integration",
        "hmac"
      ],
      "fit": "Services that let other platforms act for their users with per-item consent and signed event delivery.",
      "notFor": "Single-app setups with no partners.",
      "platforms": [
        "firebase-functions",
        "node"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "partner records (allowed return addresses, webhook address)",
        "rate limits",
        "retry timing"
      ],
      "customisable": "medium",
      "customiseNotes": "Plan logic is separate from the handlers, but the grant and link shapes follow Testers Walkthrough projects.",
      "location": {
        "repo": "testcards-toolkit",
        "paths": [
          "server/functions/lib/partnerHandlers.js",
          "server/functions/lib/partnerPlan.js",
          "server/functions/lib/partnerEvents.js",
          "server/tools/partner.js",
          "docs/partner-guide.md"
        ]
      },
      "tests": [
        "tests/job25-partner-auth.test.js",
        "tests/job25-partner-cards.test.js",
        "tests/job25-partner-grants.test.js",
        "tests/job25-partner-links.test.js",
        "tests/job25-partner-security.test.js",
        "tests/job25-partner-webhooks.test.js",
        "tests/job25-partner-test-kit.test.js"
      ]
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "tw-points-credit",
      "name": "Points to credit",
      "summary": "A partner adds credit to a linked account in exchange for points earned elsewhere, with a rate setting, a monthly cap, safe retries (one result per ref) and a 24-hour take-back of unspent credit.",
      "app": "testers-walkthrough",
      "status": "building",
      "category": "payments",
      "tags": [
        "credits",
        "points",
        "rewards",
        "partners",
        "wallet"
      ],
      "fit": "Two apps that want rewards earned in one to be spent in the other, without moving money.",
      "notFor": "Cash payouts or anything that needs real money to move.",
      "platforms": [
        "firebase-functions",
        "node"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "credits per point",
        "monthly cap",
        "take-back window"
      ],
      "customisable": "medium",
      "customiseNotes": "Rate and cap are settings; the account link follows the partner link-up block. Built on branch job26-credit, not live yet.",
      "location": {
        "repo": "testcards-toolkit",
        "paths": [
          "server/functions/lib/partnerCredit.js (branch job26-credit)",
          "server/functions/lib/partnerCreditPlan.js (branch job26-credit)",
          "docs/briefs/job26-partner-credit.md"
        ]
      },
      "tests": "none"
    },
    {
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-24",
      "sellable": "later",
      "id": "tw-one-install",
      "name": "One install command",
      "summary": "One command that safely downloads the kit (checked), connects the account, makes the app, adds the tag, writes starter cards and lists every file it touched.",
      "app": "testers-walkthrough",
      "status": "built",
      "category": "testing",
      "tags": [
        "installer",
        "onboarding",
        "developer-tools",
        "cli",
        "setup"
      ],
      "fit": "Developer tools that want a one-step, checked setup inside a customer's project.",
      "notFor": "End-user apps installed from an app store.",
      "platforms": [
        "node"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [],
      "settings": [
        "what the installer adds",
        "release channel"
      ],
      "customisable": "low",
      "customiseNotes": "Steps are written for the Testers Walkthrough kit and its file layout.",
      "location": {
        "repo": "testcards-toolkit",
        "paths": [
          "results/plugin/install.js",
          "results/lib/getStarted.js",
          "server/tools/house-install.js"
        ]
      },
      "tests": [
        "tests/job24-install.test.js"
      ]
    },
    {
      "id": "feature-switches",
      "name": "Feature switches",
      "summary": "Turns parts of an app on or off live from App Monitor, without a new release: one settings document the app reads, a useFeature('name') check in screens, and a staff-only call to flip a switch. Anything unknown counts as off.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "staff",
      "tags": [
        "feature-flags",
        "admin",
        "staff",
        "rollout",
        "settings",
        "app-monitor"
      ],
      "fit": "Apps that want to hide unfinished parts, or switch a part off quickly if something goes wrong, from one staff screen.",
      "notFor": "Per-user experiments or A/B testing with audiences and percentages.",
      "platforms": [
        "react-native",
        "expo",
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firestore",
        "cloud-functions",
        "firebase-auth"
      ],
      "settings": [
        "switch names and their defaults",
        "which staff can flip them"
      ],
      "customisable": "high",
      "customiseNotes": "Master in app-market blocks/feature-switches (no app names): switch names, doc path, the database and a demo check are settings. Apps copy it with tools/blocks-pull.js (Find My Buddy's copy still in its src/blocks/feature-switches until it swaps). The staff call that flips a switch (Admin SDK) is the app's own.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/feature-switches/"
        ]
      },
      "tests": [
        "blocks/feature-switches/test/featureLogic.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "dating-like-match",
      "name": "Like + mutual match",
      "summary": "Like people and match when both like each other, shown as swipe cards and a grid, with a daily like limit and matches that end if nobody says hi.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "dating",
      "tags": [
        "dating",
        "matching",
        "likes",
        "social",
        "profiles"
      ],
      "fit": "Social or dating apps where two people must both opt in before they can talk.",
      "notFor": "Apps without people profiles, or open group events.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "daily like limit (and who has none)",
        "match ends after (hours)",
        "age and distance limits",
        "who to show (genders)",
        "where it saves (storage)",
        "demo people and preview mode",
        "open chat and upgrade actions",
        "look (theme colours, gradient, radii, sizes)",
        "Like / Pass always visible at the bottom (stickyActions, with space for a tab bar)",
        "light colours inside the pop-ups on a dark app (popupForceLight, popupLightTheme, popupLink)",
        "all words (ready for translation)"
      ],
      "customisable": "high",
      "customiseNotes": "Master 1.4.0 in app-market blocks/match: rules (fits, candidates, like / pass, daily limit, mutual match, expiry, extend a match, unmatch), a device store and three screens (BrowseView cards / grid + match pop-up, MatchesList, PersonCard); 1.2.4: both pop-ups use the shared pop-up look (blocks/_shared/popupLook.js: PopupCard / PopupSection); 1.2.5: that shared file also has PopupChip, a section value and a danger button (the popup-look master block re-exports it; match itself unchanged). 1.2.6: colour popupLink (empty = link) for links inside the pop-ups (Keep browsing), and settings popupForceLight + popupLightTheme + popupWrapInside: everything inside the pop-ups and the grid person sheet (PersonCard too) draws light even on a dark theme (shared helper popupLook.lightInsideTheme, as popup-look's forceLight). 1.3.0: tap through photos: the big photo (card and Grid person sheet) shows every person.photos entry, tap right / left halves (labelled buttons, arrow keys on web), dots show which (colours photoDot / photoDotActive, sizes photoDot / photoDotGap / photoDotsInset, words nextPhoto / previousPhoto / photoCount, setting photosWrap); one photo as before, hidden hides all, tiles and avatars keep photos[0]. 1.4.0: online dot (person.onlineNow or setting isOnline; green #22C55E with a white ring) on the card, Grid tiles, Grid sheet and Matches avatars; a Filters button opening a Filters pop-up from the popup-look parts in the strong-edge look by default (settings filterSections / filterValues / filterPerson / onFilterChange / onFilterApply / onFilterReset / filterLook / filterTheme; same section shape as the map block). Plain React Native StyleSheet; every colour, radius and size comes from the theme setting (with this block's own slots) and every text from its words setting. App side only: a server half must decide matches and count likes later. FMB's copy still in src/blocks/match/ (to be swapped for the master and checked).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/match/"
        ]
      },
      "tests": [
        "blocks/match/test/logic.test.js",
        "blocks/match/test/matchScreens.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "selfie-check-badge",
      "name": "Selfie photo check badge",
      "summary": "A badge showing a person’s profile photo matches a live selfie: checked by staff first, automatic (paid) later.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "safety",
      "tags": [
        "safety",
        "verification",
        "selfie",
        "trust",
        "dating",
        "moderation"
      ],
      "fit": "Apps where people meet strangers and need to trust profile photos.",
      "notFor": "Formal ID checks for age or money (use a proper ID provider).",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "in-app-video-call",
      "name": "In-app video call",
      "summary": "A video call inside the app between two matched people, only when both agree; nothing is recorded.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "dating",
      "tags": [
        "video",
        "calls",
        "dating",
        "safety",
        "social"
      ],
      "fit": "Apps where people should talk safely before meeting in person.",
      "notFor": "Group calls, recording, or calls between strangers who have not matched.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "public-venue-nudge",
      "name": "Public venue nudge",
      "summary": "Warns when a chosen meeting place looks like a home address and suggests a public place instead.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "safety",
      "tags": [
        "safety",
        "meetups",
        "location",
        "dating"
      ],
      "fit": "Apps where people arrange to meet in person.",
      "notFor": "Delivery or home-service apps where a home address is expected.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "scam-money-warning",
      "name": "Scam money warning in chat",
      "summary": "Shows a warning in chat when a message asks for money, gift cards or crypto.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "safety",
      "tags": [
        "safety",
        "scams",
        "chat",
        "moderation",
        "dating"
      ],
      "fit": "Apps with private chat between people who do not know each other.",
      "notFor": "Payment or marketplace chats where talking about money is normal.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "points-system",
      "name": "Points system",
      "summary": "A points balance with a weekly top-up; earned by turning up, inviting friends or giving feedback, and bought in packs. Points are never cashed out.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "growth",
      "tags": [
        "points",
        "rewards",
        "engagement",
        "dating",
        "social"
      ],
      "fit": "Social apps that want to reward good behaviour and sell small extras without real-money balances.",
      "notFor": "Anything that needs cash payouts (use the wallet or Stripe Connect).",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating games); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "quiz-match-percent",
      "name": "Quiz + match %",
      "summary": "An optional quiz (a short starter quiz plus a daily question) that gives two people a match percentage.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "dating",
      "tags": [
        "quiz",
        "matching",
        "dating",
        "engagement",
        "profiles"
      ],
      "fit": "Apps that match people and want a fun way to show how well they fit.",
      "notFor": "Formal tests or anything where a score decides access.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating games); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "timed-event-rooms",
      "name": "Timed event rooms",
      "summary": "Speed-dating style rooms: short timed chats at a set time, then each person picks who they want to see again.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "dating",
      "tags": [
        "events",
        "chat",
        "dating",
        "matching",
        "timed"
      ],
      "fit": "Apps that run scheduled social events where people meet several others quickly.",
      "notFor": "Open-ended group chats or one-to-one chats with no time limit.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating games); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "blind-reveal",
      "name": "Blind reveal",
      "summary": "Profile photos stay hidden until a reveal rule is met: both tap reveal, after a number of messages, or at the meet.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "dating",
      "tags": [
        "dating",
        "privacy",
        "photos",
        "matching",
        "safety"
      ],
      "fit": "Dating or social apps that want people to connect on words before looks.",
      "notFor": "Apps where seeing a photo first is needed for safety or identification.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "which reveal rules are offered",
        "default rule",
        "messages before reveal",
        "look (theme colours, radii, sizes)",
        "all words (ready for translation)"
      ],
      "customisable": "high",
      "customiseNotes": "Master 1.0.0 in app-market blocks/blind-reveal: the three reveal rules (both tap, after N messages, both say we met), startReveal from the settings, and the RevealBar for the top of a blind chat. Plain React Native StyleSheet; colours, radius and sizes from the theme setting, every text from its words setting. App side only: a server half must keep the reveal record later. FMB's copy still in src/blocks/blind-reveal/ (to be swapped for the master and checked).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/blind-reveal/"
        ]
      },
      "tests": [
        "blocks/blind-reveal/test/logic.test.js",
        "blocks/blind-reveal/test/revealBar.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "popup-look",
      "name": "Pop-up look",
      "summary": "One card style for every pop-up and sheet: strong top edge, round close, boxed sections with CAPS labels, chips and buttons, all set by the theme.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "ui",
        "popup",
        "theme",
        "design",
        "dark-mode"
      ],
      "fit": "Apps with many pop-ups, sheets or filter panels that should all look the same, set once in one theme.",
      "notFor": "Full-screen pages or web-only apps without React Native.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "look (theme colours, radii, sizes for card, close, sections, labels, chips, buttons)",
        "force light inside (pop-ups stay light in dark mode) and the light colours used",
        "optional wrapper around the insides (the app's own light scope)",
        "all words (close icon and label, ready for translation)"
      ],
      "customisable": "high",
      "customiseNotes": "Master 1.0.1 in app-market blocks/popup-look: PopupCard (title row + round close), PopupSection (boxed part, CAPS label, optional value on the right), PopupChip (pale, strong colour when picked), PopupButton (main or danger), PopupClose; settings theme, words, forceLight + lightTheme + wrapInside (everything inside a pop-up draws light even in dark mode). The parts are the shared file blocks/_shared/popupLook.js (one source: match 1.2.6 uses the same file); this block adds the settings. 1.0.1: the force-light theme is made by the shared helper lightInsideTheme (match 1.2.6 uses the same one); behaviour unchanged. Neutral defaults from the base theme; an app's exact look only through settings (README Strong-edge example). FMB's own copy still in src/blocks/brand/popupLook.js (to be swapped for the master and checked: docs/note-fmb-popup-look.md).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/popup-look/"
        ]
      },
      "tests": [
        "blocks/popup-look/test/popupParts.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-27",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "map",
      "name": "Map with pins, bubbles and rough areas",
      "summary": "A map of the pins your app gives it: number bubbles that split as you zoom in, rough-area circles for people (never the exact spot), layer switches, a Filters pop-up and a detail pop-up when a pin is tapped.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "maps",
      "tags": [
        "map",
        "pins",
        "clusters",
        "privacy",
        "filters",
        "location"
      ],
      "fit": "Apps that show places, events or people on a map, where some positions must stay rough (people near me).",
      "notFor": "Turn-by-turn directions, live tracking, or apps that need offline maps.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native",
        "leaflet"
      ],
      "needs": [
        "Leaflet 1.9.4 from a CDN (loaded inside the map page, no package)",
        "on phones: the app's WebView component (e.g. react-native-webview)"
      ],
      "settings": [
        "layers (kinds, labels, on/off, pin colours)",
        "filter sections (chips, one or several choices) and what happens on change / apply / reset",
        "what a tapped pin does (your own page, the detail pop-up, its lines, sections and main button, or your own inside)",
        "rough-area rounding (grid, shift, circle size)",
        "start position and zoom limits, bubble size",
        "map tiles and their credit line, Leaflet CDN files",
        "phone WebView",
        "look (default strong-edge pop-up look, or neutral; every colour, radius and size)",
        "all words (ready for translation)"
      ],
      "customisable": "high",
      "customiseNotes": "Master 1.0.0 in app-market blocks/map: MapView (web: the map page in an iframe; phones: the app's WebView), number bubbles (zoom-based grid clusterer: same-kind bubbles never touch, tap zooms in, same-spot pins spread out or open a list, optional flag badge), layer switch row (pill chips, ✓ label when on), Filters pop-up and pin detail pop-up made only from the shared popup-look parts (PopupCard, PopupSection with CAPS label, PopupChip, PopupButton). Detail pop-up: default title + photo / emoji + the app's lines / sections + an optional main button, or the app's own renderDetail; onOpen(pin) for an app's own page. PRIVACY: approx pins are rounded by the pure approxPoint (grid cell + fixed per-id shift: stable, never the exact spot, within maxApproxMiles) before anything reaches the map page and drawn as a rough-area circle (about 1 mile wide); the same function is exported for the app's SERVER, which must round people first (the block is a second guard). Default look = the strong-edge pop-up look (lookBase 'neutral' for the plain base); every slot through the theme setting. Install: node tools/blocks-pull.js map --into <your app>/src/blocks/map --settings <your app>/src/app-config/map.js, fill the settings file, call applyMap() at start-up, draw <MapView pins={...} />. FMB's map still in its own screen (to be swapped for the master: docs/note-fmb-map.md).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/map/"
        ]
      },
      "tests": [
        "blocks/map/test/logic.test.js",
        "blocks/map/test/mapScreens.test.js",
        "functions/test/masters-pull.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-27",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "app-sales",
      "name": "App sales + subscriptions",
      "summary": "Lets developers sell apps and subscriptions through the marketplace's own checkout, with payouts to developers and a 20% cut kept by the marketplace.",
      "app": "launchyard",
      "status": "planned",
      "category": "payments",
      "tags": [
        "payments",
        "checkout",
        "subscriptions",
        "marketplace",
        "payouts",
        "stripe"
      ],
      "fit": "Marketplaces that want to sell developers' apps or subscriptions directly and pay developers out.",
      "notFor": "Apps sold only through the app stores' own billing.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "stripe",
        "firestore",
        "cloud-functions",
        "firebase-auth"
      ],
      "settings": [
        "our cut (20%)",
        "start when the marketplace has 1,000 developers"
      ],
      "customisable": "unknown",
      "customiseNotes": "Planned (Mike, 25 Oct); needs the AppQuay Stripe account and Stripe Connect. Plan: docs/plan-selling-blocks.md.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/plan-selling-blocks.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-quotes",
      "name": "Labora quotes",
      "summary": "Quote wizard for tradespeople, optional AI wording, and a client quote link where the customer views and accepts or declines.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "quotes",
        "wizard",
        "trades",
        "uk",
        "ai",
        "client-link"
      ],
      "fit": "A trades or services app where workers price jobs and send customers a quote to accept.",
      "notFor": "Simple fixed-price checkout or e-commerce carts.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [
        "quote steps and quick mode",
        "AI wording on or off"
      ],
      "customisable": "low",
      "customiseNotes": "Wizard is spread over many files in src/components/wizard and planner; VAT, trade wording and Labora collection names are baked in. The client view screen sits in the repo root. Not sure of the full file list.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/components/wizard/",
          "ClientQuoteViewScreen.js",
          "functions/quoteWording.js",
          "functions/quoteWordingPlan.js",
          "functions/quoteDecision.js"
        ]
      },
      "tests": [
        "tests/unit/quoteWordingPlan.test.js",
        "tests/unit/quoteDecisionPlan.test.js",
        "tests/unit/quoteWizardV2.test.js",
        "tests/unit/quoteSteps5859.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-invoices-reminders",
      "name": "Labora invoices + payment reminders",
      "summary": "Invoice numbering, totals and issuer details, a client invoice view, mark-as-paid and automatic payment reminders.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "invoices",
        "reminders",
        "payments",
        "trades",
        "uk"
      ],
      "fit": "A trades or freelance app that issues invoices and chases late payment.",
      "notFor": "Card or subscription billing handled by a payment provider.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "email-provider"
      ],
      "settings": [
        "reminder timing"
      ],
      "customisable": "medium",
      "customiseNotes": "Pure maths and numbering are in src/utils (invoiceMaths, invoiceNumber, invoiceIssuer) and are easy to lift; the view screen and reminder/paid server code carry Labora wording and collections. The row named markInvoicePaid.js at repo root, which does not exist; paid logic is in functions/invoicePaid.js and invoicePaidPlan.js (Not sure that is all of it).",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/utils/invoiceMaths.js",
          "src/utils/invoiceNumber.js",
          "src/utils/invoiceIssuer.js",
          "ClientInvoiceViewScreen.js",
          "functions/invoicePaid.js",
          "functions/invoicePaidPlan.js",
          "functions/invoiceReminders.js",
          "functions/invoiceRemindersPlan.js"
        ]
      },
      "tests": [
        "tests/unit/invoiceMaths.test.js",
        "tests/unit/invoiceNumber.test.js",
        "tests/unit/invoiceIssuer.test.js",
        "tests/unit/invoicePaidPlan.test.js",
        "tests/unit/invoiceRemindersPlan.test.js",
        "tests/unit/markInvoicePaid.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-expenses-receipts",
      "name": "Labora expenses + receipt scanning",
      "summary": "Log job expenses and read a receipt photo with AI to fill in the lines.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "expenses",
        "receipts",
        "ocr",
        "ai",
        "trades"
      ],
      "fit": "An app where workers record costs per job and want receipts read for them.",
      "notFor": "Full bookkeeping or bank feeds.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "Client screen JobExpenses.js is in the repo root; server reader is functions/receiptReader.js. The row named receiptService.js, which does not exist; Not sure where the client-side receipt upload lives.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "JobExpenses.js",
          "src/utils/jobExpenses.js",
          "functions/receiptReader.js",
          "functions/receiptPlan.js"
        ]
      },
      "tests": [
        "tests/unit/jobExpensesClient.test.js",
        "tests/unit/receiptPlan.test.js",
        "tests/unit/receiptItems.test.js",
        "tests/unit/receiptNoFakeScan.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-cis-vat",
      "name": "Labora CIS + VAT",
      "summary": "UK Construction Industry Scheme deduction maths, a CIS invoice developer with VAT reverse charge, and planner tax helpers.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "cis",
        "vat",
        "tax",
        "uk",
        "invoices",
        "construction"
      ],
      "fit": "A UK trades or construction app that issues invoices to contractors under CIS.",
      "notFor": "Non-UK tax or payroll.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [],
      "settings": [],
      "customisable": "medium",
      "customiseNotes": "Mostly pure utilities in src/utils (cis, cisInvoiceMaker, plannerTax), reasonably easy to lift, but rules are hard-coded to current UK rates and Labora fields. Check the rates before reuse. The row named root-level cisInvoiceMaker.js and plannerTax.js; they live in src/utils.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/utils/cis.js",
          "src/utils/cisInvoiceMaker.js",
          "src/utils/plannerTax.js"
        ]
      },
      "tests": [
        "tests/unit/cis.test.js",
        "tests/unit/cisInvoiceMaker.test.js",
        "tests/unit/plannerTax.test.js",
        "tests/unit/taxYear.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-accounts-accountant-link",
      "name": "Labora accounts summary + accountant link",
      "summary": "Income and expense ledger by period, a summary, and a secure link or portal for an accountant to view the books.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "accounts",
        "ledger",
        "accountant",
        "tax",
        "uk",
        "portal"
      ],
      "fit": "A small-business app that wants a period summary and a way to share it with an accountant.",
      "notFor": "Real double-entry accounting software.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "period choices"
      ],
      "customisable": "low",
      "customiseNotes": "Ledger and period maths are in src/utils; the accountant hub and portal are large root-level screens with Labora wording and collections.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "PrivateAccountantHub.js",
          "AccountantPortalScreen.js",
          "src/utils/accountsLedger.js",
          "src/utils/accountsPeriod.js",
          "src/utils/accountsSummary.js",
          "src/utils/accountantLink.js",
          "functions/accountantLinks.js"
        ]
      },
      "tests": [
        "tests/unit/accountsLedger.test.js",
        "tests/unit/accountsPeriod.test.js",
        "tests/unit/accountsSummary.test.js",
        "tests/unit/accountantLink.test.js",
        "tests/unit/accountantToken.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-wallet-topups",
      "name": "Labora wallet + card top-ups",
      "summary": "In-app wallet topped up by card through Stripe, with a ledger, fee plan and a Stripe webhook that credits it.",
      "app": "labora",
      "status": "built",
      "category": "payments",
      "tags": [
        "wallet",
        "stripe",
        "top-up",
        "payments",
        "ledger"
      ],
      "fit": "An app with a prepaid balance the user spends inside the app.",
      "notFor": "Direct card payment per sale, or holding customer money for others.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "stripe"
      ],
      "settings": [
        "top-up amounts",
        "fees"
      ],
      "customisable": "low",
      "customiseNotes": "The Stripe webhook (stripeWebhook) sits in the very large functions/index.js. The row was vague (wallet screens): Not sure which client screens are the full set. Stripe is live, so any reuse needs a careful review.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/cardTopUps.js",
          "functions/cardTopUpAmount.js",
          "functions/walletLedgerPlan.js",
          "functions/walletFeePlan.js",
          "functions/walletGuard.js",
          "functions/index.js",
          "src/utils/walletTopUps.js",
          "WalletDashboardModal.js"
        ]
      },
      "tests": [
        "tests/unit/cardTopUps.test.js",
        "tests/unit/walletLedgerPlan.test.js",
        "tests/unit/walletLedgerPlanServer.test.js",
        "tests/unit/walletFeePlanServer.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-ai-credits",
      "name": "Labora AI credits",
      "summary": "Pay-as-you-go credits that meter and charge for built-in AI features, with a view of the balance.",
      "app": "labora",
      "status": "built",
      "category": "ai",
      "tags": [
        "ai",
        "credits",
        "metering",
        "pay-as-you-go"
      ],
      "fit": "An app that offers AI features and wants to charge users by use.",
      "notFor": "Free unmetered AI or subscription-only billing.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [
        "prices per feature",
        "free tester allowance"
      ],
      "customisable": "medium",
      "customiseNotes": "Server core is functions/aiCredits.js (runPaidAi) with a plan file; feature names and the Labora wallet it charges are baked in.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/aiCredits.js",
          "functions/aiCreditsPlan.js"
        ]
      },
      "tests": [
        "tests/unit/aiCreditsPlan.test.js",
        "tests/unit/aiCreditsView.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-job-map-radar",
      "name": "Labora job map / radar",
      "summary": "Post a job and find jobs or people nearby on a map with pins, clusters, radius and privacy-safe positions.",
      "app": "labora",
      "status": "built",
      "category": "maps",
      "tags": [
        "map",
        "radar",
        "jobs",
        "location",
        "marketplace"
      ],
      "fit": "A local marketplace that shows nearby listings on a map.",
      "notFor": "Turn-by-turn navigation or live tracking.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firestore",
        "maps"
      ],
      "settings": [
        "radius",
        "pin clustering"
      ],
      "customisable": "low",
      "customiseNotes": "The main screen UnifiedJobsScreen.js is huge and mixed with the jobs list; map parts are in src/components/dashboard (JobRadarMap, NativeJobsMap) plus map utils. Not sure of the full set of utils.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "UnifiedJobsScreen.js",
          "src/components/dashboard/"
        ]
      },
      "tests": [
        "tests/unit/mapClusters.test.js",
        "tests/unit/mapPins.test.js",
        "tests/unit/mapJobPrivacy.test.js",
        "tests/unit/mapTravelDistance.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-job-planner",
      "name": "Labora job planner",
      "summary": "Per-job planner with a notes wizard, shopping list and materials from the quote.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "planner",
        "notes",
        "shopping-list",
        "materials",
        "jobs"
      ],
      "fit": "A trades app where each job needs notes, materials and a shopping list.",
      "notFor": "General project management or task boards.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firestore"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "JobPlannerDetail.jsx is a very large root screen; helpers are in src/components/planner. Trade wording and Labora data shapes are baked in.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "JobPlannerDetail.jsx",
          "src/components/planner/"
        ]
      },
      "tests": [
        "tests/unit/plannerCards.test.js",
        "tests/unit/plannerFromWizard.test.js",
        "tests/unit/shoppingFromQuote.test.js",
        "tests/unit/plannerMoneyShop.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-my-prices-guide-prices",
      "name": "Labora my prices + guide prices",
      "summary": "Workers set their own prices and times, with guide prices and AI help to suggest figures.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "prices",
        "pricing",
        "guide-prices",
        "ai",
        "trades"
      ],
      "fit": "A trades app where workers keep a price list used in quotes.",
      "notFor": "Dynamic or market-wide pricing engines.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [
        "price lists",
        "guide data"
      ],
      "customisable": "low",
      "customiseNotes": "Guide data in src/data (surreyPrices.js, electricianGuide.js) is region and trade specific; AI is in functions/myPricesAi.js.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/components/wizard/MyPricesTimesModal.js",
          "src/components/wizard/PricesSetupWizard.js",
          "src/data/",
          "functions/myPricesAi.js",
          "functions/myPricesAiPlan.js"
        ]
      },
      "tests": [
        "tests/unit/myPricesTimes.test.js",
        "tests/unit/pricesWizard.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-diary-bookings",
      "name": "Labora diary + bookings",
      "summary": "Diary with offered dates, clash detection and fixes, and reminders.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "diary",
        "calendar",
        "bookings",
        "reminders",
        "scheduling"
      ],
      "fit": "An app where workers and customers agree dates and need clash warnings.",
      "notFor": "A full calendar sync with Google or Outlook.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "email-provider"
      ],
      "settings": [
        "reminder timing"
      ],
      "customisable": "low",
      "customiseNotes": "DiaryScreen.js is a large root screen; booking logic is in src/utils/diaryBookingCore.js and diaryBookingPlan.js, with server files functions/diaryBooking*.js and diaryReminders.js.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "DiaryScreen.js",
          "src/utils/diaryBookingCore.js",
          "src/utils/diaryBookingPlan.js",
          "functions/diaryBooking.js",
          "functions/diaryBookingPlan.js",
          "functions/diaryReminders.js"
        ]
      },
      "tests": [
        "tests/unit/diaryBookingPlan.test.js",
        "tests/unit/diaryBookingServer.test.js",
        "tests/unit/diaryBusyPlan.test.js",
        "tests/unit/diaryTime.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-team-worker-management",
      "name": "Labora team / worker management",
      "summary": "Employers add workers who get their own login, see assigned jobs and can be removed.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "team",
        "workers",
        "staff",
        "accounts",
        "permissions"
      ],
      "fit": "An app where a business adds staff accounts under its own account.",
      "notFor": "Open sign-up or marketplace freelancers.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "Account creation and removal are server callables in functions/workerAccounts.js; WorkerManagementPortal.js is a big root screen. Roles and rules are Labora specific.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "WorkerManagementPortal.js",
          "functions/workerAccounts.js",
          "functions/workerRules.js"
        ]
      },
      "tests": [
        "tests/unit/workerRules.test.js",
        "tests/unit/workerJobs.test.js",
        "tests/unit/workerDocPermission.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-qr-job-signoff",
      "name": "Labora QR sign-off of finished jobs",
      "summary": "Worker finishes a job and the customer signs it off through a token link or QR code, recording the sign-off.",
      "app": "labora",
      "status": "built",
      "category": "business",
      "tags": [
        "signoff",
        "qr",
        "jobs",
        "verification",
        "customer"
      ],
      "fit": "A trades or service app that needs customer confirmation that work is complete.",
      "notFor": "Contracts needing legal e-signatures.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "FinishJobModal.js and VerifyJobPage.js are root screens; token logic is in functions/signoffByToken.js. A second, different FinishJobModal exists under src/components/planner.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "FinishJobModal.js",
          "VerifyJobPage.js",
          "functions/signoffByToken.js",
          "functions/signoffByTokenPlan.js"
        ]
      },
      "tests": [
        "tests/unit/signoffByTokenPlan.test.js",
        "tests/unit/signoffToken.test.js",
        "tests/unit/signoffRecord.test.js",
        "tests/unit/signoffLock72.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-reviews-stars",
      "name": "Labora reviews + stars",
      "summary": "Star ratings and written reviews tied to completed jobs, one per job, with review invites, reporting and staff actions.",
      "app": "labora",
      "status": "built",
      "category": "safety",
      "tags": [
        "reviews",
        "ratings",
        "stars",
        "moderation",
        "trust"
      ],
      "fit": "A marketplace where only real customers can review a worker.",
      "notFor": "Open anonymous reviews.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "Review code is spread over several client files and many server files (reviewStats, reviewOwed, staffReviewAction, pastReviews, linkReviewsStaff). The row said \"review screens + functions\"; Not sure of the full client list.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "TradesmanReviews.js",
          "reviewDispatcher.js",
          "src/components/review/",
          "functions/reviewStats.js",
          "functions/reviewOwed.js",
          "functions/staffReviewAction.js"
        ]
      },
      "tests": [
        "tests/unit/reviewRules.test.js",
        "tests/unit/reviewOnePerJob.test.js",
        "tests/unit/reviewStatsPlan.test.js",
        "tests/unit/selfReviewGuard.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-kyc-credential-checks",
      "name": "Labora ID + certificate checks (KYC)",
      "summary": "Workers submit trade credentials and certificates, staff review them, and expiry is tracked.",
      "app": "labora",
      "status": "built",
      "category": "safety",
      "tags": [
        "kyc",
        "verification",
        "certificates",
        "credentials",
        "trust"
      ],
      "fit": "A trades marketplace that wants proof of insurance or qualifications before a worker is trusted.",
      "notFor": "Automated identity verification (this is manual staff review).",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "staff review screens"
      ],
      "customisable": "low",
      "customiseNotes": "Server submit is functions/kycSubmit.js. The review screens live in App Monitor (Approval Hub, KYC pages), a different repo, so only half of this block is here. Not sure of the exact client form files.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/kycSubmit.js",
          "functions/kycSubmitPlan.js",
          "functions/credentialReviewPlan.js",
          "functions/credentialExpiryPlan.js"
        ]
      },
      "tests": [
        "tests/unit/kycSubmitPlan.test.js",
        "tests/unit/kycForms.test.js",
        "tests/unit/credentialReviewPlan.test.js",
        "tests/unit/credentialExpiryPlan.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-report-a-problem",
      "name": "Labora report a problem",
      "summary": "Users report a problem with screenshots and a note; the report is stored for staff.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "feedback",
        "bug-report",
        "screenshots",
        "support"
      ],
      "fit": "Any app that wants an in-app problem report with screenshots.",
      "notFor": "Full ticket systems. A general version exists as the Testers Walkthrough kit, which is a better starting point.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "medium",
      "customiseNotes": "Client helper src/utils/problemReport.js and server functions/feedbackSubmit.js; Labora wording and collections are baked in.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/utils/problemReport.js",
          "functions/feedbackSubmit.js",
          "functions/feedbackPlan.js"
        ]
      },
      "tests": [
        "tests/unit/problemReport.test.js",
        "tests/unit/feedbackPlan.test.js",
        "tests/unit/feedback.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-secure-share-links",
      "name": "Labora secure share links",
      "summary": "Create one-time links so a customer or accountant can open and act on one item without an account.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "secure-link",
        "sharing",
        "one-time",
        "token",
        "access"
      ],
      "fit": "An app that shares a single quote, invoice or report with someone outside the app.",
      "notFor": "Permanent public pages.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "link expiry"
      ],
      "customisable": "medium",
      "customiseNotes": "Server in functions/secureLinks.js (createSecureLink, openSecureLink, actOnSecureLink). Project notes mention a TTL policy on link docs; Not sure it is set. Client panel is SecureLinkPanel.js.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/secureLinks.js",
          "src/components/secureLinks/SecureLinkPanel.js"
        ]
      },
      "tests": [
        "tests/unit/secureLinkPlan.test.js",
        "tests/unit/secureLinkView.test.js",
        "tests/unit/secureLinkShare.test.js",
        "tests/unit/secureLinkParity.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-early-access-gate",
      "name": "Labora early access / coming-soon gate",
      "summary": "Limits the app to approved testers while a setting is on, with chosen features left open and a notice for others.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "early-access",
        "gate",
        "feature-flag",
        "beta",
        "coming-soon"
      ],
      "fit": "An app in a closed beta that must restrict access to named testers.",
      "notFor": "Per-user paid plan gating.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firestore"
      ],
      "settings": [
        "early access flag",
        "open features list"
      ],
      "customisable": "medium",
      "customiseNotes": "Logic is src/utils/earlyAccess.js and reads Labora settings and tester documents; collection names are hard-coded. Server notice in functions/earlyAccessNotice.js.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "src/utils/earlyAccess.js",
          "src/components/earlyAccess/",
          "functions/earlyAccessNotice.js"
        ]
      },
      "tests": [
        "tests/unit/earlyAccess.test.js",
        "tests/unit/earlyAccessNotice.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-referrals-promoter",
      "name": "Labora referrals + promoter portal",
      "summary": "Referral codes are claimed at sign-up and credited to promoters, who have their own portal.",
      "app": "labora",
      "status": "built",
      "category": "growth",
      "tags": [
        "referrals",
        "promoter",
        "affiliate",
        "growth"
      ],
      "fit": "An app that rewards people for bringing in users.",
      "notFor": "Complex multi-level or paid-commission schemes.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "low",
      "customiseNotes": "claimReferral is exported from functions/index.js with plan in referralPlan.js and referralAttribution.js; the portal is PromoterPortalScreen.js (named in the row) plus src/components/promoter. Not sure of the exact set of client files.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/referralPlan.js",
          "functions/referralAttribution.js",
          "functions/index.js",
          "PromoterPortalScreen.js",
          "src/components/promoter/"
        ]
      },
      "tests": [
        "tests/unit/referralPlan.test.js",
        "tests/unit/promoterPortalCopy.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-account-deletion",
      "name": "Labora account deletion",
      "summary": "Deletes an account with refunds handled and legally required records kept for six years.",
      "app": "labora",
      "status": "built",
      "category": "accounts",
      "tags": [
        "account-deletion",
        "gdpr",
        "retention",
        "privacy",
        "uk"
      ],
      "fit": "An app handling money that must let users delete while keeping records the law requires.",
      "notFor": "Simple apps with no records to keep.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "retention period"
      ],
      "customisable": "medium",
      "customiseNotes": "Server in functions/accountDeletion.js with plan and data files, retention rules in src/utils/retention.js and functions/retention.js. The row named DeleteAccountModal.js, which is not at repo root; Not sure where the client modal lives now.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/accountDeletion.js",
          "functions/accountDeletionPlan.js",
          "functions/accountDeletionData.js",
          "src/utils/retention.js",
          "functions/retention.js"
        ]
      },
      "tests": [
        "tests/unit/accountDeletionPlan.test.js",
        "tests/unit/accountDeletionData.test.js",
        "tests/unit/retention.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-push-email-notifications",
      "name": "Labora push notifications + email",
      "summary": "Notifies users by push and email on new jobs, messages, quotes and applications, with per-user preferences.",
      "app": "labora",
      "status": "built",
      "category": "app-basics",
      "tags": [
        "notifications",
        "push",
        "email",
        "fcm",
        "preferences"
      ],
      "fit": "An app on Firebase that notifies users when documents change.",
      "notFor": "Marketing email campaigns.",
      "platforms": [
        "firebase-functions",
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "email-provider"
      ],
      "settings": [
        "push preferences"
      ],
      "customisable": "low",
      "customiseNotes": "Many notify triggers live inside the huge functions/index.js, with templates in emailTemplates.js and the service worker firebase-messaging-sw.js. Email delivery needs a configured provider; Not sure of its current state.",
      "location": {
        "repo": "trademanapp",
        "paths": [
          "functions/index.js",
          "functions/pushPrefs.js",
          "functions/pushTokens.js",
          "functions/emailTemplates.js",
          "firebase-messaging-sw.js"
        ]
      },
      "tests": [
        "tests/unit/pushPrefs.test.js",
        "tests/unit/pushGroup.test.js",
        "tests/unit/emailTemplates.test.js",
        "tests/unit/emailShell.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "labora-photo-folders",
      "name": "Photo folders",
      "summary": "Take or attach photos on a job, labelled by section or room, kept in Before, Progress and Finished folders per job.",
      "app": "labora",
      "status": "planned",
      "category": "business",
      "tags": [
        "photos",
        "folders",
        "jobs",
        "before-after",
        "camera"
      ],
      "fit": "Job or project apps where people photograph work as it goes and need the pictures sorted by stage and area.",
      "notFor": "Apps that only need one profile picture, or general photo galleries with no jobs.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "file-storage"
      ],
      "settings": [
        "folder names",
        "section or room labels"
      ],
      "customisable": "unknown",
      "customiseNotes": "Planned for Labora's job photos feature (Mike, 26 Oct); built after Labora's current release. Not built yet: only the row in the shared list.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/building-blocks.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "labora-portfolio-customer-ok",
      "name": "Portfolio with customer OK",
      "summary": "Pick job photos (before/after pairs, own choice, later “in use”) for a public portfolio; the customer gives one-tap consent first, and only the town is shown.",
      "app": "labora",
      "status": "planned",
      "category": "business",
      "tags": [
        "portfolio",
        "photos",
        "consent",
        "before-after",
        "privacy",
        "trust"
      ],
      "fit": "Trade or service apps where workers show past jobs publicly and need the customer’s OK first.",
      "notFor": "Showing photos without the customer’s consent, or showing a full address.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "file-storage"
      ],
      "settings": [
        "consent wording",
        "what location is shown (town only)"
      ],
      "customisable": "unknown",
      "customiseNotes": "Planned for Labora's job photos feature (Mike, 26 Oct); built after Labora's current release. Not built yet: only the row in the shared list.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/building-blocks.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "labora-pictures-in-documents",
      "name": "Pictures in documents",
      "summary": "Adds an “Add pictures” button to each quote or document section, using that section’s photos; one photo shows “Add picture”, none shows no button.",
      "app": "labora",
      "status": "planned",
      "category": "business",
      "tags": [
        "photos",
        "quotes",
        "documents",
        "invoices"
      ],
      "fit": "Apps that make quotes or reports in sections and already keep photos by section.",
      "notFor": "Documents with no sections, or apps without job photos.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "file-storage"
      ],
      "settings": [
        "button words"
      ],
      "customisable": "unknown",
      "customiseNotes": "Planned for Labora's job photos feature (Mike, 26 Oct); built after Labora's current release. Not built yet: only the row in the shared list.",
      "location": {
        "repo": "app-market",
        "paths": [
          "docs/building-blocks.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26"
    },
    {
      "id": "auto-18-id-check",
      "name": "Automatic 18+ ID check",
      "summary": "An automatic age and ID check (Stripe Identity) before someone can use the app; under-18s are blocked. Different from staff-checked ID.",
      "app": "sports-betting-ai",
      "status": "planned",
      "category": "safety",
      "tags": [
        "age-check",
        "id-check",
        "verification",
        "compliance",
        "safety",
        "stripe"
      ],
      "fit": "Apps that must prove every user is 18 or over without staff checking each one.",
      "notFor": "Checking trade credentials or business documents (use staff-checked KYC).",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript"
      ],
      "needs": [
        "stripe",
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned in the Sports Betting AI app; not built. Plan: sports-betting-ai-recommender/docs/plan.md.",
      "location": {
        "repo": "sports-betting-ai-recommender",
        "paths": [
          "docs/plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "wallet-paid-tiers",
      "name": "Tiers paid from the wallet",
      "summary": "A monthly subscription tier charged from the shared Wallet (backed by auto top-up); each tier unlocks its own screens, and too little balance drops the person to Free with reminders.",
      "app": "sports-betting-ai",
      "status": "planned",
      "category": "payments",
      "tags": [
        "subscriptions",
        "tiers",
        "wallet",
        "payments",
        "access"
      ],
      "fit": "Apps that sell monthly tiers but want one credit balance instead of a card subscription.",
      "notFor": "Apps that need plain card subscriptions with no wallet.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript"
      ],
      "needs": [
        "stripe",
        "firestore",
        "cloud-functions",
        "firebase-auth"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned in the Sports Betting AI app; not built. Plan: sports-betting-ai-recommender/docs/plan.md.",
      "location": {
        "repo": "sports-betting-ai-recommender",
        "paths": [
          "docs/plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "honest-ai-track-record",
      "name": "Honest AI track record",
      "summary": "Every AI pick is logged automatically with its result, wins and losses, and the full record is shown to users; no hand-picked stats.",
      "app": "sports-betting-ai",
      "status": "planned",
      "category": "ai",
      "tags": [
        "ai",
        "transparency",
        "trust",
        "stats",
        "predictions"
      ],
      "fit": "Apps that give AI predictions or advice and want to show an honest record of how they turned out.",
      "notFor": "Apps where results cannot be checked later.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned in the Sports Betting AI app; not built. Plan: sports-betting-ai-recommender/docs/plan.md.",
      "location": {
        "repo": "sports-betting-ai-recommender",
        "paths": [
          "docs/plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "ai-web-search-answers",
      "name": "AI web search answers",
      "summary": "Claude looks things up live with web search and explains the answer in plain English; the cost is passed on as wallet credits.",
      "app": "sports-betting-ai",
      "status": "planned",
      "category": "ai",
      "tags": [
        "ai",
        "search",
        "claude",
        "answers",
        "wallet"
      ],
      "fit": "Apps where people ask questions that need up-to-date facts from the web.",
      "notFor": "Answers that must come only from your own data.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript"
      ],
      "needs": [
        "anthropic-key",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned in the Sports Betting AI app; not built. Plan: sports-betting-ai-recommender/docs/plan.md.",
      "location": {
        "repo": "sports-betting-ai-recommender",
        "paths": [
          "docs/plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "stats-prediction-model",
      "name": "Stats prediction model",
      "summary": "Works out team ratings and expected goals (xG) from match data, gives percentage chances for each result, and compares them with the bookmaker's odds. Checked on past seasons before use.",
      "app": "sports-betting-ai",
      "status": "planned",
      "category": "ai",
      "tags": [
        "sports",
        "predictions",
        "statistics",
        "xg",
        "odds",
        "models"
      ],
      "fit": "Any sports app that wants fair, data-based chances for upcoming matches.",
      "notFor": "Sports without enough past match data to check the model.",
      "platforms": [
        "firebase-functions",
        "node"
      ],
      "stack": [
        "javascript"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned in the Sports Betting AI app; not built. Plan: sports-betting-ai-recommender/docs/plan.md.",
      "location": {
        "repo": "sports-betting-ai-recommender",
        "paths": [
          "docs/plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "recorded-show-room",
      "name": "Recorded show room",
      "summary": "Records a group video call, makes an AI first cut with captions for the host to edit, and staff approve it before it airs. Viewers guess the outcome for points. Everyone signs consent at recording and clips can be removed on request.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "dating",
      "tags": [
        "video",
        "recording",
        "shows",
        "editing",
        "moderation",
        "consent",
        "points",
        "dating"
      ],
      "fit": "Apps that want show-style content made from real group calls, checked before anyone sees it.",
      "notFor": "Live broadcasts to an audience (this block is recorded and approved first).",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating plan G9-G14); changed 25 Oct from a live show room to a recorded one; not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "clip-consent-kickback",
      "name": "Clip + consent + kick back",
      "summary": "Makes short clips from shows; each person ticks consent and can withdraw it later; clips can go to the app’s social media, and contestants or hosts are rewarded with points or a share, per sign-up.",
      "app": "find-my-buddy",
      "status": "planned",
      "category": "growth",
      "tags": [
        "clips",
        "consent",
        "social-media",
        "rewards",
        "creators",
        "privacy"
      ],
      "fit": "Apps that turn moments into shareable clips and want clear consent and rewards for the people in them.",
      "notFor": "Clips of people who have not given consent, or apps without a way to withdraw it.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [],
      "customisable": "unknown",
      "customiseNotes": "Planned for Find My Buddy v2 (dating plan G9-G14); not built. Plan: find-my-buddy/docs/dating-plan.md.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/dating-plan.md"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "test-bots",
      "name": "Test bots",
      "summary": "Fills a testers-only database with bot members (marked isBot), including some around each tester's town: bots reply in chat with scripted lines, accept joins as hosts, join testers' activities and post or react. One switch turns them on or off, and one command removes them all before launch.",
      "app": "find-my-buddy",
      "status": "building",
      "category": "testing",
      "tags": [
        "testing",
        "bots",
        "seed-data",
        "social",
        "qa",
        "app-monitor"
      ],
      "fit": "Social apps that need a lively-feeling app for testers before there are real users.",
      "notFor": "The live app with real users (bots must be removed before launch).",
      "platforms": [
        "firebase-functions",
        "node"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "features.bots switch (App Monitor)",
        "bot count and towns"
      ],
      "customisable": "medium",
      "customiseNotes": "Being built in Find My Buddy; scripted replies and activity types follow Find My Buddy's data. Remove-all command: tools/bots.js.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "functions/bots/",
          "tools/bots.js"
        ]
      },
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "app": "launchyard",
      "status": "built",
      "category": "growth",
      "platforms": [
        "web"
      ],
      "stack": [
        "javascript"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "share-buttons",
      "name": "Share buttons",
      "summary": "WhatsApp, Facebook, X, email and copy-link buttons, plus the phone share sheet when there is one. Plain share addresses, no trackers.",
      "tags": [
        "share",
        "social",
        "whatsapp",
        "links",
        "web"
      ],
      "fit": "Any web page that people should pass on.",
      "notFor": "Native apps that already use the system share sheet only.",
      "needs": [],
      "settings": [
        "which buttons and their order",
        "phone share sheet on/off",
        "link parameter for a member code (ref)",
        "every button word and screen-reader label",
        "how long \"Link copied.\" shows"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/share-buttons (no app names): one browser module, makeShareButtons(settings) -> shareBar, shareButton (one button: phone share sheet or a small menu), withRef. An app copies it with tools/blocks-pull.js and keeps one settings file; the look is the app's CSS for the share-* classes (share.css is a neutral start).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/share-buttons/",
          "public/share-buttons/",
          "public/app-config/share-buttons.js",
          "public/share.js",
          "public/styles.css (.share-bar, .share-one)"
        ]
      },
      "tests": [
        "blocks/share-buttons/test/share.test.js",
        "functions/test/share-buttons.test.js"
      ],
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "app": "launchyard",
      "status": "built",
      "category": "accounts",
      "platforms": [
        "web"
      ],
      "stack": [
        "javascript"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-27",
      "id": "social-sign-in",
      "name": "Sign in with Google, Apple, Microsoft or GitHub",
      "summary": "\"Continue with Google / Apple / Microsoft / GitHub\" buttons next to your email sign-up, over Firebase Auth. Pop-up first, full-page redirect when pop-ups are blocked, plain-English errors.",
      "tags": [
        "sign-in",
        "google",
        "apple",
        "microsoft",
        "github",
        "firebase",
        "web"
      ],
      "fit": "Any web app on Firebase Auth that wants one-tap sign-up.",
      "notFor": "Apps not on Firebase Auth; native apps (they need each provider's own SDK).",
      "needs": [
        "Firebase Auth (web, modular SDK)",
        "each provider switched on in the Firebase console (Apple needs a paid Apple Developer account)"
      ],
      "settings": [
        "which providers and their order",
        "pop-up or redirect",
        "extra scopes per provider",
        "the \"or use your email\" line on/off",
        "every button word and error message"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/social-sign-in (no app names, no keys): makeSocialSignIn(settings) -> mount(el, { au, auth }), signIn(id), finishRedirect(), message(error). The app passes in its own Firebase Auth module; the look is the app's CSS for the social-* classes (social.css is a neutral start). Every button has a test-card target sign-in-<id>.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/social-sign-in/",
          "public/social-sign-in/",
          "public/app-config/social-sign-in.js",
          "public/social.js"
        ]
      },
      "tests": [
        "blocks/social-sign-in/test/social.test.js",
        "functions/test/social-sign-in.test.js"
      ],
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "app": "launchyard",
      "status": "built",
      "category": "growth",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "referral-codes",
      "name": "Promo codes and referrals",
      "summary": "Every member gets their own code; share links carry ?ref=CODE; the server records who brought whom and pays points once, on the new person's first action, never on sign-up or to yourself.",
      "tags": [
        "referral",
        "promo-code",
        "invite",
        "points",
        "growth"
      ],
      "fit": "Apps that reward members for bringing in active new people.",
      "notFor": "Cash rewards (needs a payments block and tax checks).",
      "needs": [
        "firestore",
        "cloud-functions",
        "firebase-auth"
      ],
      "settings": [
        "points per new person",
        "code prefix",
        "how new an account must be to claim (days)",
        "collection names",
        "every server message",
        "creditPoints(tx, uid, n): how points are paid",
        "which events count as the first action (the app's triggers)",
        "page: device keys, link parameter, call names, Account box ids and words"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/referral-codes (no app names): server.js makeReferrals (myCode, claim, firstAction; test actions pay nothing) and page.js startReferrals (keeps the ?ref= code, claims it after sign-in, share links with the member code, Account box). An app copies it with tools/blocks-pull.js (server and page folders), keeps its settings files, passes creditPoints, and wires its own callables and first-action triggers.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/referral-codes/",
          "functions/referral-codes/",
          "public/referral-codes/",
          "functions/app-config/referral-codes.json",
          "public/app-config/referral-codes.js",
          "functions/index.js (Promo codes / referrals section)",
          "public/referral.js",
          "public/account.html (#referral-box)"
        ]
      },
      "tests": [
        "blocks/referral-codes/test/server.test.js",
        "blocks/referral-codes/test/page.test.js",
        "functions/test/referral.test.js",
        "functions/test/env-tags.test.js",
        "functions/test/share-feed.emulator.js"
      ],
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "app": "launchyard",
      "status": "built",
      "category": "app-basics",
      "platforms": [
        "web"
      ],
      "stack": [
        "javascript"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "bottom-menu",
      "name": "Bottom menu",
      "summary": "A fixed bottom menu on phones and computers, with the current page marked; the items come from the app's settings.",
      "tags": [
        "navigation",
        "menu",
        "tabs",
        "mobile",
        "web"
      ],
      "fit": "Multi-page web apps that want app-style navigation.",
      "notFor": "Single-screen tools.",
      "needs": [],
      "settings": [
        "menu items (label, page, which pages mark it, icon)",
        "screen-reader name",
        "class names",
        "only on pages that have a given element",
        "tester-kit target names",
        "icon size"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/bottom-menu (no app names): one browser module, startBottomMenu(settings). An app copies it with tools/blocks-pull.js and keeps one settings file with its items; the look is the app's CSS for the bottom-nav classes (menu.css is a neutral start).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/bottom-menu/",
          "public/bottom-menu/",
          "public/app-config/bottom-menu.js",
          "public/nav.js",
          "public/styles.css (.bottom-nav)"
        ]
      },
      "tests": [
        "blocks/bottom-menu/test/menu.test.js",
        "functions/test/bottom-menu.test.js"
      ],
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "app": "launchyard",
      "status": "built",
      "category": "community",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "member-feed",
      "name": "Member feed",
      "summary": "Signed-in members post text, up to 4 pictures and a link; posts show straight away with Like, Comment, Share and Report; owners edit or delete; staff hide reported posts.",
      "tags": [
        "feed",
        "posts",
        "community",
        "comments",
        "likes",
        "moderation"
      ],
      "fit": "Apps with a community that should talk to each other.",
      "notFor": "Apps that must check every post before it shows.",
      "needs": [
        "firestore",
        "firebase-auth",
        "cloud-storage",
        "cloud-functions"
      ],
      "settings": [
        "free posts and comments a day + time zone",
        "paid option on/off (later)",
        "most pictures, text and comment length",
        "picture size and folder",
        "posts per page",
        "report reasons",
        "call and collection names",
        "sign-in page and shared-post address",
        "every page and server message"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/member-feed (no app names): server.js makeFeed (posts and comments made by the server with a daily free allowance, picture clean-up) and page.js initFeed (the whole feed page). An app copies it with tools/blocks-pull.js (server and page folders), keeps its settings files, wires two callables and a clean-up trigger, and must copy the Firestore + Storage rules from the README (staff hide/dismiss is the app's own call).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/member-feed/",
          "functions/member-feed/",
          "public/member-feed/",
          "functions/app-config/member-feed.json",
          "public/app-config/member-feed.js",
          "functions/index.js (Feed section)",
          "public/feed.js",
          "public/feed.html",
          "firestore.rules (feedPosts, feedReports)",
          "storage.rules (feed/)"
        ]
      },
      "tests": [
        "blocks/member-feed/test/server.test.js",
        "blocks/member-feed/test/page.test.js",
        "functions/test/feed.test.js",
        "functions/test/page-env.test.js",
        "functions/test/share-feed.emulator.js"
      ],
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "promo-offers",
      "name": "Staff promo codes + dynamic sign-up offers",
      "summary": "Staff make promo codes (expiry, max uses, one per person, who it is for, stacks with the sign-up offer or not). Every day the server suggests a sign-up offer per type of person from sign-ups against a target and week on week, inside staff limits; staff approve it, and new people apply it with one tap. Optional add-on: cross-app codes (XP-...) given in one app and used in another, checked by a shared hub register; people matched by a hashed verified email, no money moved by the hub.",
      "app": "launchyard",
      "status": "built",
      "category": "growth",
      "tags": [
        "promo-code",
        "offers",
        "discounts",
        "growth",
        "sign-up",
        "points",
        "free-trial",
        "staff",
        "cross-app"
      ],
      "fit": "An app that wants staff-run promo codes and a sign-up offer that grows when sign-ups are slow and shrinks when growth is good.",
      "notFor": "Taking payment itself: money-off offers are only saved on the account for the app's own checkout to use.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "person types",
        "offer kinds (money off saved / points / free trial) and their wording",
        "new-account window (days)",
        "suggestion weights (target vs growth) and rounding step",
        "collection prefix",
        "page words and sign-up links",
        "hooks: creditPoints, typesFor, countSignups",
        "cross-app add-on: hub address, app id, code prefix, hub kind -> app kind map (app key + pepper are secrets)"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/promo-offers (no app names); an app copies server.js, page.js and page.css unchanged and fills in one settings.json plus three small hooks. Staff screens are not in the block (the app's admin calls wrap the staff functions). Cross-app add-on: copy xpromo.js too, fill settings.crossApp and set the two secrets (app key, pepper).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/promo-offers/",
          "functions/promo-offers/",
          "public/promo-offers/",
          "public/offers.js",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/promo-offers.test.js",
        "functions/test/promo-offers.emulator.js",
        "functions/test/xpromo.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "more-apps",
      "name": "More from us (more apps)",
      "summary": "Shows the developer's other apps from a public list with a 1-hour cache and a local fallback: a page, an occasional feed card the member can close, and a banner slot. Never lists the app itself.",
      "app": "find-my-buddy",
      "status": "built",
      "category": "growth",
      "tags": [
        "cross-promotion",
        "more-apps",
        "feed",
        "promotion"
      ],
      "fit": "A developer with several apps who wants each app to point people to the others.",
      "notFor": "Apps that should not show other apps or ads.",
      "platforms": [
        "react-native",
        "expo",
        "web"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "public apps list (JSON)"
      ],
      "settings": [
        "feedUrl",
        "selfIds / selfHosts",
        "fallback list",
        "feed spacing (firstAfter, every)",
        "cacheMs",
        "words"
      ],
      "customisable": "high",
      "customiseNotes": "Clean block with neutral defaults; the app fills settings with configureMoreApps (FMB: src/fmb-config/more-apps.js). Reads the AppQuay apps.json shape { version, updated, apps:[{ id, name, pitch, logoUrl, link, pageUrl, category, mikesOwn }] }; apps.json is not live yet, so the fallback list is used.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/more-apps/"
        ]
      },
      "tests": [
        "src/blocks/more-apps/moreApps.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25"
    },
    {
      "id": "maker-stats",
      "name": "Developer stats",
      "summary": "Per-listing stats for its owner: visits and unique visitors, likes and dislikes, clicks to the app and tester results, for the last 7 days, 30 days and all time. Counted by the server only, once per visitor per day.",
      "tags": [
        "stats",
        "analytics",
        "makers",
        "dashboard",
        "privacy",
        "counts"
      ],
      "fit": "A catalogue or marketplace where each owner should see how their own listing is doing, without tracking people.",
      "notFor": "Full web analytics (funnels, sources, per-person history) or counting on sites you do not run.",
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "collection prefix",
        "time zone for days",
        "item id pattern",
        "new counts per network address per day",
        "periods (days)",
        "hooks: ownerOf, testerResults"
      ],
      "customiseNotes": "Master in blocks/maker-stats (no app names); an app copies server.js, page.js and page.css unchanged, fills in settings.json, passes ownerOf (and optionally testerResults), adds two read rules and one index. The app checks the reader owns the item before read().",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/maker-stats/",
          "functions/maker-stats/",
          "public/maker-stats/",
          "public/stats-hit.js",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/stats-swipe.test.js",
        "functions/test/stats-swipe.emulator.js"
      ],
      "app": "launchyard",
      "status": "built",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "customisable": "high",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "category": "growth"
    },
    {
      "id": "swipe-discover",
      "name": "Swipe Discover",
      "summary": "One listing at a time with Like, Skip, Visit and Share (swipe, tap or arrow keys). It learns from likes and the person's interests; signed-out likes stay on the device and are saved to the account after sign-in. Includes a My likes list.",
      "tags": [
        "discover",
        "swipe",
        "likes",
        "recommendations",
        "browse",
        "share"
      ],
      "fit": "A catalogue that wants a quick, phone-friendly way to browse and like listings.",
      "notFor": "Search or filtering by many fields, or recommendations from other people's behaviour.",
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "collection prefix",
        "item id pattern",
        "max items per list",
        "max items saved after sign-in",
        "page words",
        "days before a skipped or seen item comes back",
        "hooks: itemOk, onChange"
      ],
      "customiseNotes": "Master in blocks/swipe-discover (no app names); an app copies server.js, page.js and page.css unchanged, fills in settings.json, passes loadItems/readMine/readInterests/share on the page and itemOk/onChange on the server, and adds one read rule.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/swipe-discover/",
          "functions/swipe-discover/",
          "public/swipe-discover/",
          "public/discover.js",
          "public/discover.html",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/stats-swipe.test.js",
        "functions/test/stats-swipe.emulator.js"
      ],
      "app": "launchyard",
      "status": "built",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "customisable": "high",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "category": "growth"
    },
    {
      "id": "blocks-catalogue",
      "name": "Blocks catalogue page",
      "summary": "A public catalogue page of reusable parts: collapsed cards, search as you type, category and status filters, staff edits shown on top of the list file, a form for approved developers to send in their own parts, and staff calls to edit cards, set hidden prices and approve developers' items (approved ones show marked as from a developer).",
      "app": "launchyard",
      "status": "built",
      "tags": [
        "catalogue",
        "search",
        "filters",
        "marketplace",
        "staff",
        "admin",
        "directory"
      ],
      "fit": "An app or site that lists reusable parts, templates or plugins from a JSON file and wants staff to edit and approve them.",
      "notFor": "Selling or checkout (buying is not built here), or large product shops that need a search server.",
      "platforms": [
        "web",
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions",
        "firebase-auth"
      ],
      "settings": [
        "list file address",
        "statuses and their names",
        "categories from the list file or settings",
        "app names",
        "show prices (off) and price format",
        "text limits",
        "collection names and counter prefix",
        "staff edits and open counts on/off",
        "every page word",
        "currencies, per, developer share, sales note",
        "developers' form choices (categories, platforms, licences), sends per day, form words"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/blocks-catalogue (no app names); an app copies find.js, page.js, page.css, maker-form.js (web) and server.js (functions) unchanged, puts its settings.json next to both, renders into one element, and wraps the staff calls behind its own staff check and the developer send behind its own approved-developer check. Needs public-read rules for the cards and maker-cards collections and an own-read rule for developers' items.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/blocks-catalogue/",
          "public/blocks-catalogue/",
          "functions/blocks-catalogue/",
          "public/blocks.js",
          "public/blocks.html",
          "functions/index.js",
          "functions/maker-blocks.js",
          "public/maker.js"
        ]
      },
      "tests": [
        "functions/test/blocks-page.test.js",
        "functions/test/blocks-admin.test.js",
        "functions/test/maker-blocks.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "category": "business",
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "request-env",
      "name": "Test or live tag (request env)",
      "summary": "The server tags every doc, report and feed item it writes with env test or live, from the request Origin (then Referer): localhost, 127.0.0.1 and Firebase preview channel hosts are test, anything else live. Never from the request body. Admin lists take an optional env filter.",
      "tags": [
        "env",
        "test-link",
        "preview",
        "staff",
        "filter",
        "firebase"
      ],
      "fit": "Apps whose test address and live site share one database and whose staff dashboard needs a Live / Test switch.",
      "notFor": "Security decisions: a non-browser caller can send any Origin, so env is a label only.",
      "needs": [
        "cloud-functions"
      ],
      "settings": [
        "extra test hosts (optional)",
        "test host pattern (optional)"
      ],
      "customiseNotes": "Master in blocks/request-env (env.js, no app names). Copy env.js unchanged; call envFromRequest(req) in each callable and store env on the doc; triggers copy envOf(source doc); admin lists use envFilter(data) + filterByEnv(items, want).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/request-env/",
          "functions/request-env/",
          "functions/index.js"
        ]
      },
      "tests": [
        "functions/test/request-env.test.js",
        "functions/test/env-tags.test.js"
      ],
      "app": "launchyard",
      "status": "built",
      "platforms": [
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "customisable": "low",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "app-basics",
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "blocks-mcp",
      "name": "Claude connector for building blocks (MCP server)",
      "summary": "A public MCP server: a developer adds one web address to Claude (or another AI app) and their AI can search the building blocks, read one card and get a free block's install steps while it builds their app. With the developer's own key it can also send, list and update their own blocks.",
      "app": "launchyard",
      "status": "built",
      "tags": [
        "mcp",
        "claude",
        "ai",
        "connector",
        "catalogue",
        "search",
        "api",
        "rate-limit"
      ],
      "fit": "A catalogue of reusable parts, templates or plugins that people want their own AI to search and install from.",
      "notFor": "Taking money, or private catalogues that need sign-in (searching is read-only; only a person's own items can be changed, with their key).",
      "platforms": [
        "firebase-functions"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "server name, title and instructions",
        "statuses and app names",
        "tool names, titles and descriptions",
        "limits: calls a minute and a day, results, text lengths, days kept",
        "collection names for limits and counts",
        "hidden card fields (price)",
        "install command wording",
        "every message"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/blocks-mcp (no app names); an app copies it with tools/blocks-pull.js, fills one settings file, makes bundle.json (its catalogue + the masters' READMEs) with bundle.js before each deploy, and wires one onRequest function with its own hooks for developers' public cards, staff edits, its test address and the caller address. Optional account tools (send / list / update your own items) appear only with a valid key, through the app's own hooks (e.g. the api-keys block). Licence keys for paid and developers' blocks are not built yet.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/blocks-mcp/",
          "functions/blocks-mcp/",
          "functions/app-config/blocks-mcp.json",
          "functions/app-config/blocks-mcp-bundle.json",
          "functions/index.js",
          "public/blocks.html"
        ]
      },
      "tests": [
        "functions/test/blocks-mcp.test.js",
        "functions/test/dev-api.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "ai",
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "api-keys",
      "name": "Personal API keys",
      "summary": "Let a member make their own API keys so their AI or script can use their account: the key is shown once with a Copy button, only a salted hash and the last 4 characters are kept, up to 5 keys each, revoke works at once, and every key has its own limit per minute and per day.",
      "app": "launchyard",
      "status": "built",
      "tags": [
        "api",
        "keys",
        "ai",
        "mcp",
        "developers",
        "security",
        "rate-limit"
      ],
      "fit": "An app whose members (developers, sellers, partners) want their own AI, script or connector to act for them through a web API.",
      "notFor": "Signing people in to the app itself (use normal sign-in), or keys shared between many people.",
      "platforms": [
        "firebase-functions",
        "web"
      ],
      "stack": [
        "javascript",
        "node-22"
      ],
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "key start letters and length",
        "keys per account",
        "calls per minute and per day per key",
        "collection names",
        "every message and page word",
        "callable names"
      ],
      "customisable": "high",
      "customiseNotes": "Master in blocks/api-keys (server.js + page.js, no app names); an app copies it with tools/blocks-pull.js, fills its settings, decides who may make keys and what a key may do, and gives the key collections no client rule. AppQuay uses it for developers sending and updating their own blocks (web API devApi + the Claude connector).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/api-keys/",
          "functions/api-keys/",
          "public/api-keys/",
          "functions/app-config/api-keys.json",
          "public/app-config/api-keys.js",
          "functions/dev-api.js",
          "functions/index.js",
          "public/maker.js"
        ]
      },
      "tests": [
        "functions/test/dev-api.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "accounts",
      "usedBy": [
        "launchyard"
      ]
    },
    {
      "id": "ask-how",
      "name": "Ask how",
      "category": "ai",
      "summary": "An \"Ask\" button and a small box where a person types what they want to do; the app maker's own AI picks a way through the app's marked places and Show me lights it step by step. When there is no way it says so and offers \"Report it as missing\".",
      "app": "testers-walkthrough",
      "status": "building",
      "tags": [
        "help",
        "how-to",
        "ai",
        "guide",
        "onboarding",
        "walkthrough",
        "search"
      ],
      "fit": "An app that already marks its buttons for Testers Walkthrough (data-testcard) and wants people to ask \"how do I...?\" and be shown the way.",
      "notFor": "Apps with no marked places, or answering questions about the person's own data (only the app's marked places are used).",
      "platforms": [
        "web",
        "react",
        "expo",
        "react-native"
      ],
      "stack": [
        "javascript",
        "typescript",
        "react",
        "react-native"
      ],
      "needs": [
        "cloud-functions",
        "anthropic-key"
      ],
      "settings": [
        "all words (one text object)",
        "question limit (up to 300 characters)",
        "how the question is sent (ask)",
        "how the way is lit (onShow)",
        "where a missing report goes (onReport)",
        "the current screen name (page)",
        "own stylesheet (plain)"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master with three variants on one shared core (ask-core.js). The block sends nothing itself: the app passes ask, onShow and onReport. The answer comes from the Testers Walkthrough askHow server function with the maker's own AI key (Claude, OpenAI or Gemini); the server is not live yet.",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/ask-how/"
        ]
      },
      "tests": [
        "functions/test/ask-how.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-27"
    },
    {
      "app": "find-my-buddy",
      "status": "built",
      "stack": [
        "javascript",
        "react-native"
      ],
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "tests": [
        "src/blocks/who-viewed-you/logic.test.js",
        "functions/test/whoViewed.test.js",
        "functions/test-emulator/whoViewed.emulator.test.js",
        "tests/rules/pro-extras.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "who-viewed-you",
      "name": "Who viewed you",
      "summary": "Shows paid members who looked at their profile, with a privacy opt-out (browse without being shown) and a 30-day clean-up of view records.",
      "tags": [
        "profiles",
        "views",
        "premium",
        "privacy",
        "social"
      ],
      "fit": "Social, dating or networking apps with member profiles and a paid tier.",
      "notFor": "Apps without member profiles, or where being seen must stay private.",
      "needs": [
        "firestore",
        "cloud-functions"
      ],
      "settings": [
        "paid plan that unlocks it",
        "opt-out switch",
        "keep views (30 days)"
      ],
      "customisable": "medium",
      "customiseNotes": "Built in Find My Buddy (not live yet); FMB settings in src/fmb-config/.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/who-viewed-you/",
          "src/fmb-config/"
        ]
      },
      "category": "dating",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "app": "find-my-buddy",
      "status": "built",
      "stack": [
        "javascript",
        "react-native"
      ],
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "tests": [
        "src/blocks/advanced-filters/filterLogic.test.js",
        "tests/rules/pro-extras.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "advanced-filters",
      "name": "Advanced filters",
      "summary": "Extra search filters for paid members, with saved filter sets they can reuse.",
      "tags": [
        "search",
        "filters",
        "premium",
        "saved-searches"
      ],
      "fit": "Apps with member or listing search and a paid tier.",
      "notFor": "Apps with a small, simple list that needs no filtering.",
      "needs": [
        "firestore"
      ],
      "settings": [
        "which filters are paid",
        "max saved sets"
      ],
      "customisable": "medium",
      "customiseNotes": "Built in Find My Buddy (not live yet); FMB settings in src/fmb-config/.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/advanced-filters/",
          "src/fmb-config/"
        ]
      },
      "category": "dating",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "app": "find-my-buddy",
      "status": "built",
      "stack": [
        "javascript",
        "react-native"
      ],
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "tests": [
        "src/blocks/org-accounts/orgLogic.test.js",
        "src/fmb-config/org-accounts.test.js",
        "functions/test/orgAccounts.test.js",
        "functions/test-emulator/clubs.emulator.test.js",
        "tests/rules/clubs.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "org-accounts",
      "name": "Club / business accounts",
      "summary": "Organisation profiles (clubs, businesses) with their own admins, a staff-verified badge, a free trial then a business plan, and regular classes made from group repeats.",
      "tags": [
        "organisations",
        "business",
        "clubs",
        "verification",
        "plans",
        "classes"
      ],
      "fit": "Community or activity apps where clubs and businesses run groups or classes.",
      "notFor": "Apps for individuals only.",
      "needs": [
        "firestore",
        "cloud-functions",
        "auth"
      ],
      "settings": [
        "trial length",
        "business plan price",
        "who can verify (staff)"
      ],
      "customisable": "medium",
      "customiseNotes": "Built in Find My Buddy (not live yet); FMB settings in src/fmb-config/org-accounts.js; verification is done by staff (App Monitor).",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "src/blocks/org-accounts/",
          "functions/ (server part)",
          "src/fmb-config/org-accounts.js"
        ]
      },
      "category": "accounts",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "app": "find-my-buddy",
      "status": "built",
      "stack": [
        "javascript",
        "react-native"
      ],
      "platforms": [
        "web",
        "react-native",
        "expo",
        "docs"
      ],
      "tests": "none",
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-25",
      "id": "legal-pages",
      "name": "Legal pages",
      "summary": "Templates for a privacy policy, terms, community rules, a safety guide and an Online Safety Act checklist. Drafts for a solicitor to review, not legal advice.",
      "tags": [
        "legal",
        "privacy",
        "terms",
        "safety",
        "online-safety-act",
        "templates"
      ],
      "fit": "Any UK app with members or user content that needs its legal pages.",
      "notFor": "Use without a solicitor checking the final wording.",
      "needs": [],
      "settings": [
        "company name and contact",
        "what the app collects"
      ],
      "customisable": "high",
      "customiseNotes": "Built as DRAFTS (docs only, no tests) in Find My Buddy docs/legal/: privacy-policy, terms, community-rules, safety-guide, online-safety-act-checklist, README + generic templates/. Every page is marked DRAFT - for solicitor review, not legal advice. Awaiting solicitor before any app uses them live.",
      "location": {
        "repo": "find-my-buddy",
        "paths": [
          "docs/legal/"
        ]
      },
      "category": "app-basics",
      "usedBy": [
        "find-my-buddy"
      ]
    },
    {
      "id": "photo-folders",
      "name": "Photo folders",
      "summary": "Take or attach photos for a job or project, labelled by section or room, kept in folders (Before / Progress / Finished). Each photo is shrunk in the browser to 2560 px at JPEG 0.85 with a 400 px preview.",
      "app": "labora",
      "status": "built",
      "tags": [
        "photos",
        "camera",
        "upload",
        "folders",
        "images",
        "resize",
        "privacy"
      ],
      "fit": "Any app where people photograph work, items or places and want them sorted by stage and room.",
      "notFor": "Photo editing, video, or public galleries without a consent step.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-storage"
      ],
      "settings": [
        "folders and titles",
        "kept size and JPEG quality",
        "preview size",
        "size limits",
        "storage path root",
        "all words",
        "theme"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master: settings and a storage/database adapter are passed in; the app writes its own storage rules. Labora copy built and tested on trademanapp feat/job-photos (693e07e3), not live (27 Oct).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/photo-folders/"
        ]
      },
      "tests": [
        "blocks/photo-folders/test/logic.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "business"
    },
    {
      "id": "doc-pictures",
      "name": "Pictures in documents",
      "summary": "An \"Add pictures\" button on each section of a quote or report, listing the photos with that section's label: one photo gives \"Add picture\", none gives no button.",
      "app": "labora",
      "status": "built",
      "tags": [
        "photos",
        "documents",
        "quotes",
        "reports",
        "images"
      ],
      "fit": "Apps that make quotes, reports or proposals with sections and want the matching photos in them.",
      "notFor": "Free-form page layout or image editing.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "most pictures per section",
        "most pictures per document",
        "all words",
        "theme"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master: pure logic plus two small screen parts; the app shows the pictures to its customer. Labora copy built and tested on trademanapp feat/job-photos (693e07e3), not live (27 Oct).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/doc-pictures/"
        ]
      },
      "tests": [
        "blocks/doc-pictures/test/logic.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "business"
    },
    {
      "id": "portfolio-consent",
      "name": "Portfolio with customer OK",
      "summary": "Pick before/after pairs, chosen photos and later \"in use\" photos for a public portfolio; a photo goes public only after the customer's one-tap OK, showing the town only.",
      "app": "labora",
      "status": "built",
      "tags": [
        "portfolio",
        "photos",
        "consent",
        "privacy",
        "gdpr",
        "uk",
        "trust"
      ],
      "fit": "Trades, makers or services that want to show past work with the customer's permission.",
      "notFor": "Photos of people, or apps without a server to record the answer.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [
        "firebase-auth",
        "firestore",
        "cloud-functions",
        "cloud-storage"
      ],
      "settings": [
        "photos per request",
        "days before asking again",
        "all words",
        "theme"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master: logic runs on the server and the app; the app supplies the one-time link, the answer call and the public copy. Labora copy built and tested on trademanapp feat/job-photos (693e07e3), not live (27 Oct).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/portfolio-consent/"
        ]
      },
      "tests": [
        "blocks/portfolio-consent/test/logic.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "business"
    },
    {
      "id": "photo-markup",
      "name": "Draw on photos",
      "summary": "Draw on a photo with a pen, circle or arrow in a few colours, with undo and a short caption; saved as a separate marked copy so the original stays clean.",
      "app": "labora",
      "status": "built",
      "tags": [
        "photos",
        "markup",
        "annotate",
        "drawing",
        "images",
        "caption"
      ],
      "fit": "Apps where people point things out on photos: faults, measurements, before/after notes.",
      "notFor": "Full photo editing (crop, filters) or drawing apps.",
      "platforms": [
        "web",
        "react-native",
        "expo"
      ],
      "stack": [
        "javascript",
        "react-native"
      ],
      "needs": [],
      "settings": [
        "colours",
        "line width",
        "limits",
        "caption length",
        "all words",
        "theme"
      ],
      "customisable": "high",
      "customiseNotes": "Clean master: pure logic, a canvas renderer and a web editor; the app stores the marked copy. Native apps need their own editor (web only today). Labora copy built and tested on trademanapp feat/job-photos (693e07e3), not live (27 Oct).",
      "location": {
        "repo": "app-market",
        "paths": [
          "blocks/photo-markup/"
        ]
      },
      "tests": [
        "blocks/photo-markup/test/logic.test.js"
      ],
      "sellable": "later",
      "price": null,
      "licence": null,
      "buildingBlockSince": "2026-10-26",
      "category": "business"
    }
  ]
}
