[{"data":1,"prerenderedAt":384},["ShallowReactive",2],{"site-settings":3,"project-satchel":77,"project-logs-satchel":111,"project-articles-satchel":383},{"global":4,"header":11,"footer":27},{"brandName":5,"brandStamp":6,"brandTagline":7,"communityUrl":8,"defaultSeoDescription":9,"defaultOgImage":10},"Useful Labs","est. 2025 · UK","Serious software, built out loud.","https:\u002F\u002Fapps.usefullabs.io","A product-led lab building production-grade platforms in the open — Sonic Artistes, Castora and more. A few times a year we take that same craft to a client build. Real software, shipped at product-team pace, shared honestly.",null,{"navLinks":12,"scarcityPulse":10,"showClock":25,"ctaLabel":26,"ctaUrl":8},[13,17,20,23],{"label":14,"url":15,"external":16},"Projects","\u002Fprojects",false,{"label":18,"url":19,"external":16},"Writing","\u002Fblog",{"label":21,"url":22,"external":16},"Tools","\u002Ftools",{"label":24,"url":8,"external":25},"The Hub",true,"Join the community",{"tagline":7,"aboutParagraph":28,"copyrightLine":29,"columns":30,"legalLinks":64},"A UK product lab, built in the open. Real production software, documented honestly — no hype, no highlights reel.","© Useful Labs {year} · Made in the UK with unusual patience.",[31,41,50],{"heading":14,"links":32},[33,36,39],{"label":34,"url":35,"external":25},"Sonic Artistes","https:\u002F\u002Fapp.sonicartistes.com",{"label":37,"url":38,"external":25},"Castora","https:\u002F\u002Fgetcastora.com",{"label":40,"url":15,"external":16},"Archive",{"heading":18,"links":42},[43,44,47],{"label":18,"url":19,"external":16},{"label":45,"url":46,"external":16},"RSS feed","\u002Frss.xml",{"label":48,"url":49,"external":16},"Newsletter","#newsletter",{"heading":51,"links":52},"Elsewhere",[53,55,58,61],{"label":24,"url":54,"external":25},"https:\u002F\u002Fcommunity.usefullabs.io",{"label":56,"url":57,"external":25},"GitHub","https:\u002F\u002Fgithub.com\u002Fpaulwilliams-us",{"label":59,"url":60,"external":25},"X \u002F Twitter","https:\u002F\u002Fx.com",{"label":62,"url":63,"external":16},"Email","mailto:hello@usefullabs.io",[65,68,71,74],{"label":66,"url":67,"external":16},"Privacy","\u002Fprivacy",{"label":69,"url":70,"external":16},"Terms","\u002Fterms",{"label":72,"url":73,"external":16},"AI Policy","\u002Fai-policy",{"label":75,"url":76,"external":16},"Colophon","\u002Fcolophon",{"id":78,"slug":78,"name":79,"idLabel":80,"tagText":81,"description":82,"accent":83,"meta":86,"modules":87,"chips":95,"progress":100,"followLabel":103,"followHref":104,"siteUrl":105,"siteLabel":106,"status":107,"isFeatured":16,"isHeroFeatured":16,"publishedIso":108,"modifiedIso":109,"hero":-1,"contentHtml":109,"featuredImage":10,"screenshots":110},"satchel","Satchel","09 \u002F SA","Live","Self-hosted file store for a small circle — folders, resumable uploads, member sharing, and short public share links with QR codes. Built from an empty repo to a live, deployed product in five days.",{"token":84,"rgb":85},"violet","139, 92, 246",[],[88,89,90,91,92,93,94],"File Browser","Resumable Uploads","Sharing","Public Links & QR","Trash & Retention","Activity Events","Storage Usage",[96,97,98,99],"Nuxt 4","PocketBase","Wasabi S3","Tailwind v4",{"label":101,"percent":102},"Build progress",85,"Follow the build","\u002Fprojects\u002Fsatchel","https:\u002F\u002Fsatcheldrop.com","satcheldrop.com","live","2026-07-23T10:30:00","",[],[112,119,124,130,135,141,146,151,156,161,166,171,176,181,187,192,197,202,208,213,218,223,229,234,239,244,249,254,259,264,269,274,279,283,288,293,298,303,308,313,318,323,328,333,338,343,348,353,358,363,368,373,378],{"id":113,"slug":114,"projectId":78,"kind":115,"title":116,"contentHtml":109,"dateLabel":117,"publishedIso":118,"commitUrl":-1,"image":-1},"b90056ce-73de-4844-9bd4-433b1325c3df","sat-53-events-read-api","shipped","The events read API, and the useNotifications seam it feeds","3w ago","2026-07-28T18:35:00",{"id":120,"slug":121,"projectId":78,"kind":115,"title":122,"contentHtml":109,"dateLabel":117,"publishedIso":123,"commitUrl":-1,"image":-1},"ae42d04d-df13-42cb-abe6-7ed75f21f4d2","sat-52-preview-render-api","A server-side preview render API for text, code and markdown","2026-07-28T15:49:00",{"id":125,"slug":126,"projectId":78,"kind":127,"title":128,"contentHtml":109,"dateLabel":117,"publishedIso":129,"commitUrl":-1,"image":-1},"61e49d84-d913-4c15-8858-736d7d357f95","sat-51-migrate-down-note","noted","Migrate down is not a rollback — serve re-applies it at boot","2026-07-28T15:20:00",{"id":131,"slug":132,"projectId":78,"kind":115,"title":133,"contentHtml":109,"dateLabel":117,"publishedIso":134,"commitUrl":-1,"image":-1},"d82738bc-817b-4677-b7eb-ceb14f5085fd","sat-50-events-stream","The events stream — an stl_events collection, hook emitters and a retention policy","2026-07-28T15:16:00",{"id":136,"slug":137,"projectId":78,"kind":138,"title":139,"contentHtml":109,"dateLabel":117,"publishedIso":140,"commitUrl":-1,"image":-1},"c52bf284-dd74-492a-a99f-ccacdf664b2e","sat-49-move-picker-guard","fixed","The move picker refuses the folder the items are already in","2026-07-28T14:15:00",{"id":142,"slug":143,"projectId":78,"kind":138,"title":144,"contentHtml":109,"dateLabel":117,"publishedIso":145,"commitUrl":-1,"image":-1},"29b7fdd2-1e4d-4188-b79a-24583341fa23","sat-48-touch-and-date-papercuts","Papercuts — hover-gated row actions, a server-read retention window, and PocketBase datetimes normalised before Date.parse","2026-07-28T12:48:00",{"id":147,"slug":148,"projectId":78,"kind":138,"title":149,"contentHtml":109,"dateLabel":117,"publishedIso":150,"commitUrl":-1,"image":-1},"01ad8b16-f57e-4d68-8d7a-ec3ea1f5a38a","sat-47-api-defect-sweep","API defect sweep — 404 for unmatched \u002Fapi paths, JSON errors for every Accept header, an explicit ISO expiry grammar","2026-07-28T12:47:00",{"id":152,"slug":153,"projectId":78,"kind":115,"title":154,"contentHtml":109,"dateLabel":117,"publishedIso":155,"commitUrl":-1,"image":-1},"a0b9be50-0159-40f7-a6f1-2218b3ae1533","sat-46-egress-accounting","ZIP egress recorded per transfer — measured, not capped","2026-07-28T12:44:00",{"id":157,"slug":158,"projectId":78,"kind":138,"title":159,"contentHtml":109,"dateLabel":117,"publishedIso":160,"commitUrl":-1,"image":-1},"0fb0836c-8ac5-4220-b3a1-01c0fbcc5850","sat-45-child-count-move-sync","child_count now syncs on the destination folder for both move and undo","2026-07-28T12:43:00",{"id":162,"slug":163,"projectId":78,"kind":138,"title":164,"contentHtml":109,"dateLabel":117,"publishedIso":165,"commitUrl":-1,"image":-1},"4c353148-570a-4d1c-a273-addff5b49e76","sat-44-member-self-purge","Member self-purge, closing the folder-purge escalation path","2026-07-27T18:00:00",{"id":167,"slug":168,"projectId":78,"kind":115,"title":169,"contentHtml":109,"dateLabel":117,"publishedIso":170,"commitUrl":-1,"image":-1},"3552868a-1658-43f9-bbfd-1902e821ef60","sat-43-bulk-download-zip","Bulk download over an authenticated ZIP route","2026-07-27T15:50:00",{"id":172,"slug":173,"projectId":78,"kind":115,"title":174,"contentHtml":109,"dateLabel":117,"publishedIso":175,"commitUrl":-1,"image":-1},"3f204434-fb71-4a02-ac5d-9dd4433934b5","sat-42-drag-to-move","Drag to move — onto folders, chips and breadcrumbs alike","2026-07-27T13:05:00",{"id":177,"slug":178,"projectId":78,"kind":115,"title":179,"contentHtml":109,"dateLabel":117,"publishedIso":180,"commitUrl":-1,"image":-1},"3c506918-58c8-4ae0-86a8-28e6ccd87c54","sat-41-touch-selection","Touch selection — long press, 44px targets and a bottom action bar","2026-07-27T12:10:00",{"id":182,"slug":183,"projectId":78,"kind":115,"title":184,"contentHtml":109,"dateLabel":185,"publishedIso":186,"commitUrl":-1,"image":-1},"b5ebffb1-344e-4062-a177-3eaef2c06e03","sat-40-selection-bulk-actions","Selection and bulk actions across the file browser","4w ago","2026-07-26T21:15:00",{"id":188,"slug":189,"projectId":78,"kind":115,"title":190,"contentHtml":109,"dateLabel":185,"publishedIso":191,"commitUrl":-1,"image":-1},"a4639bcc-4231-4759-ba5c-6638e46e4b4a","sat-39-sort-view-prefs","Sort and view preferences that survive navigation","2026-07-26T19:30:00",{"id":193,"slug":194,"projectId":78,"kind":127,"title":195,"contentHtml":109,"dateLabel":185,"publishedIso":196,"commitUrl":-1,"image":-1},"d2e4721f-918e-4553-bc14-2aada95b2f9d","sat-38-first-wave-concurrency-defects","The first build wave exposed two concurrency defects — recorded before they got patched over","2026-07-26T18:25:00",{"id":198,"slug":199,"projectId":78,"kind":115,"title":200,"contentHtml":109,"dateLabel":185,"publishedIso":201,"commitUrl":-1,"image":-1},"0b269c31-df4f-49ba-a6c7-1252f3a588c5","sat-37-trash-retention","Server-owned trash retention, with a near-expiry countdown on the rows","2026-07-26T18:20:00",{"id":203,"slug":204,"projectId":78,"kind":205,"title":206,"contentHtml":109,"dateLabel":185,"publishedIso":207,"commitUrl":-1,"image":-1},"ff609065-4b12-4283-b229-e2fd0c0ff636","sat-36-filebrowser-extraction","refactored","FileBrowser broken into seams — navigation, surface states and item actions","2026-07-26T18:15:00",{"id":209,"slug":210,"projectId":78,"kind":115,"title":211,"contentHtml":109,"dateLabel":185,"publishedIso":212,"commitUrl":-1,"image":-1},"7ad8b194-5ae7-48e3-b3f4-bf72ed3dbe84","sat-35-storage-usage","Circle storage usage, measured against a soft target","2026-07-26T17:30:00",{"id":214,"slug":215,"projectId":78,"kind":127,"title":216,"contentHtml":109,"dateLabel":185,"publishedIso":217,"commitUrl":-1,"image":-1},"5613b439-0488-4886-b8b7-e173d0f218ff","sat-34-ranked-spec-set","A ranked spec set with an explicit cut line, and a build runner to drive it","2026-07-26T16:00:00",{"id":219,"slug":220,"projectId":78,"kind":127,"title":221,"contentHtml":109,"dateLabel":185,"publishedIso":222,"commitUrl":-1,"image":-1},"e1879549-b7a9-40bd-a1c4-9b36beee9de6","sat-33-engineering-skills","Vendored a set of engineering skills and configured them for this repo","2026-07-25T08:15:00",{"id":224,"slug":225,"projectId":78,"kind":115,"title":226,"contentHtml":109,"dateLabel":227,"publishedIso":228,"commitUrl":-1,"image":-1},"22989bca-0b04-4225-afa2-e1a5c21028a0","sat-32-brand","The brand mark lands — sidebar, login, favicons and the public link pages","1mo ago","2026-07-24T20:20:00",{"id":230,"slug":231,"projectId":78,"kind":138,"title":232,"contentHtml":109,"dateLabel":227,"publishedIso":233,"commitUrl":-1,"image":-1},"158c7d83-57aa-48d8-acd8-d88233672933","sat-31-serve-local-superuser","serve-local pre-creates a dev superuser so PocketBase stops auto-opening the installer","2026-07-23T23:15:00",{"id":235,"slug":236,"projectId":78,"kind":138,"title":237,"contentHtml":109,"dateLabel":227,"publishedIso":238,"commitUrl":-1,"image":-1},"5660d15a-2643-48fd-9d0e-4b74d13fef0d","sat-30-folder-delete-guard","Deleting a non-empty folder is refused — an orphaning guard","2026-07-23T23:05:00",{"id":240,"slug":241,"projectId":78,"kind":138,"title":242,"contentHtml":109,"dateLabel":227,"publishedIso":243,"commitUrl":-1,"image":-1},"6a4ef679-b25f-4784-8e93-7af6dc331731","sat-29-mobile-sweep","Mobile sweep — min-w-0 on SidebarInset kills the horizontal overflow at its root","2026-07-23T22:40:00",{"id":245,"slug":246,"projectId":78,"kind":115,"title":247,"contentHtml":109,"dateLabel":227,"publishedIso":248,"commitUrl":-1,"image":-1},"12dbfb64-2e54-470b-9218-7866cc4bbbff","sat-28-command-palette","A command palette with a band trigger, preview handoff and an owner-scoped search endpoint","2026-07-23T21:45:00",{"id":250,"slug":251,"projectId":78,"kind":115,"title":252,"contentHtml":109,"dateLabel":227,"publishedIso":253,"commitUrl":-1,"image":-1},"cee338a4-5b50-4da8-a83f-54fc6ddc3d30","sat-27-folder-chips","Folder chips with cap and expand, backed by a hook-maintained, cascade-aware child_count","2026-07-23T18:10:00",{"id":255,"slug":256,"projectId":78,"kind":115,"title":257,"contentHtml":109,"dateLabel":227,"publishedIso":258,"commitUrl":-1,"image":-1},"5ac95c8d-9668-4792-a36d-4bd2ba8dc753","sat-26-row-anatomy","Linear-style row anatomy — a contained list surface, tinted type tiles and two-line cells","2026-07-23T17:20:00",{"id":260,"slug":261,"projectId":78,"kind":115,"title":262,"contentHtml":109,"dateLabel":227,"publishedIso":263,"commitUrl":-1,"image":-1},"b4731a74-f146-4678-a3ca-264dae9966fa","sat-25-accent-tokens","Indigo accent tokens, AA-checked in light and dark, plus a file-type tint and label vocabulary","2026-07-23T16:50:00",{"id":265,"slug":266,"projectId":78,"kind":115,"title":267,"contentHtml":109,"dateLabel":227,"publishedIso":268,"commitUrl":-1,"image":-1},"4fc1eda7-0df9-4b31-b9f4-d3053ec7b0fd","sat-24-overview-line","A My Files overview line, backed by its own \u002Fapi\u002Foverview endpoint","2026-07-23T13:00:00",{"id":270,"slug":271,"projectId":78,"kind":138,"title":272,"contentHtml":109,"dateLabel":227,"publishedIso":273,"commitUrl":-1,"image":-1},"3035fa91-c7fe-4ed8-b445-6340611ba180","sat-23-house-rules-drift","Closing design house-rules drift — motion tokens, button box, focus rings and placeholders","2026-07-23T11:50:00",{"id":275,"slug":276,"projectId":78,"kind":138,"title":277,"contentHtml":109,"dateLabel":227,"publishedIso":278,"commitUrl":-1,"image":-1},"8ab7369b-0dc1-44d2-b340-e921c5e31869","sat-22-cors-lockdown","PocketBase CORS locked to the app origin","2026-07-23T11:20:00",{"id":280,"slug":281,"projectId":78,"kind":115,"title":282,"contentHtml":109,"dateLabel":227,"publishedIso":108,"commitUrl":-1,"image":-1},"5f4d29dd-29cb-43ff-96f4-c1b36a015738","sat-21-frontend-live","Satchel goes live — the Nuxt frontend deployed and verified end-to-end at satcheldrop.com",{"id":284,"slug":285,"projectId":78,"kind":115,"title":286,"contentHtml":109,"dateLabel":227,"publishedIso":287,"commitUrl":-1,"image":-1},"99a5bc2a-88d6-4798-b25d-92fe866a0dab","sat-20-api-live","PocketBase deployed and verified live at api.satcheldrop.com","2026-07-20T10:15:00",{"id":289,"slug":290,"projectId":78,"kind":138,"title":291,"contentHtml":109,"dateLabel":227,"publishedIso":292,"commitUrl":-1,"image":-1},"ac15cd6e-41ec-4fe9-97c8-92a744a87bc4","sat-19-member-removal-transfer","A departing member's items now transfer to the admin on removal, instead of orphaning","2026-07-20T10:00:00",{"id":294,"slug":295,"projectId":78,"kind":138,"title":296,"contentHtml":109,"dateLabel":227,"publishedIso":297,"commitUrl":-1,"image":-1},"e8344262-843c-4463-a627-d117a3088613","sat-18-link-expiry-timezone","Link expiry was off by one in negative-UTC zones","2026-07-20T09:40:00",{"id":299,"slug":300,"projectId":78,"kind":115,"title":301,"contentHtml":109,"dateLabel":227,"publishedIso":302,"commitUrl":-1,"image":-1},"eb26b7dd-a472-4fbd-8c3f-9266e8b5accb","sat-17-mona-sans-house-rules","Mona Sans, locally hosted, replaces Inter — plus reduced-motion, inset focus rings and a URL-persisted links filter","2026-07-20T08:45:00",{"id":304,"slug":305,"projectId":78,"kind":205,"title":306,"contentHtml":109,"dateLabel":227,"publishedIso":307,"commitUrl":-1,"image":-1},"00450195-3061-4185-8ed9-93b864d99c64","sat-16-shadcn-first","A shadcn-vue-first component pass — Sidebar, ToggleGroup, Breadcrumb, Progress, DatePicker and NumberField replace the hand-rolled ones","2026-07-20T00:55:00",{"id":309,"slug":310,"projectId":78,"kind":115,"title":311,"contentHtml":109,"dateLabel":227,"publishedIso":312,"commitUrl":-1,"image":-1},"3a85ecf6-b20d-489d-8835-7b513034d253","sat-15-link-hardening","Link hardening — rate limiting, a trash warning, dead-link handling and a fail-closed cookie secret","2026-07-19T19:10:00",{"id":314,"slug":315,"projectId":78,"kind":115,"title":316,"contentHtml":109,"dateLabel":227,"publishedIso":317,"commitUrl":-1,"image":-1},"2f026272-f3d3-4a6f-8fca-0fad47b9bdfb","sat-14-links-dashboard","A links dashboard with visit analytics and a nav badge","2026-07-19T18:15:00",{"id":319,"slug":320,"projectId":78,"kind":115,"title":321,"contentHtml":109,"dateLabel":227,"publishedIso":322,"commitUrl":-1,"image":-1},"b0799b49-75c1-4a31-b621-53078a4297b9","sat-13-qr-codes","QR codes on every link, with status badges and link indicators on the items they point at","2026-07-19T17:35:00",{"id":324,"slug":325,"projectId":78,"kind":115,"title":326,"contentHtml":109,"dateLabel":227,"publishedIso":327,"commitUrl":-1,"image":-1},"48841488-dfa3-4dca-a0b3-e6f2883a93e3","sat-12-link-zip","Streaming ZIP download for public folder links","2026-07-19T17:00:00",{"id":329,"slug":330,"projectId":78,"kind":115,"title":331,"contentHtml":109,"dateLabel":227,"publishedIso":332,"commitUrl":-1,"image":-1},"168e574e-8ee2-42b0-8d0c-d1e580e001a4","sat-11-public-links","Short public share links — password gate, byte cap and scope enforcement, all before a single byte is served","2026-07-19T16:30:00",{"id":334,"slug":335,"projectId":78,"kind":115,"title":336,"contentHtml":109,"dateLabel":227,"publishedIso":337,"commitUrl":-1,"image":-1},"13344bb4-8e11-4e64-8fd9-215499e304c8","sat-10-member-sharing","Member sharing — a shares collection, an access resolver, and a shared-with-me page","2026-07-19T14:00:00",{"id":339,"slug":340,"projectId":78,"kind":115,"title":341,"contentHtml":109,"dateLabel":227,"publishedIso":342,"commitUrl":-1,"image":-1},"86cc54f3-72be-4d48-a452-0592d6fadfea","sat-09-trash","Trash with restore, and a scheduled purge behind it","2026-07-19T03:45:00",{"id":344,"slug":345,"projectId":78,"kind":115,"title":346,"contentHtml":109,"dateLabel":227,"publishedIso":347,"commitUrl":-1,"image":-1},"9f985adb-81ce-4ead-a402-136c101f686c","sat-08-file-browser","The file browser — folders, previews and drag-drop uploads","2026-07-19T02:50:00",{"id":349,"slug":350,"projectId":78,"kind":115,"title":351,"contentHtml":109,"dateLabel":227,"publishedIso":352,"commitUrl":-1,"image":-1},"ecd98a5d-1ecf-464c-be7c-343b046b640c","sat-07-upload-pipeline","A resumable multipart upload pipeline, with a docked progress panel","2026-07-19T02:15:00",{"id":354,"slug":355,"projectId":78,"kind":115,"title":356,"contentHtml":109,"dateLabel":227,"publishedIso":357,"commitUrl":-1,"image":-1},"164aa8ab-db54-4c68-aa8e-3140bf23adf8","sat-06-storage-proxy","An auth-gated storage proxy with range support and cached thumbnails","2026-07-19T01:25:00",{"id":359,"slug":360,"projectId":78,"kind":115,"title":361,"contentHtml":109,"dateLabel":227,"publishedIso":362,"commitUrl":-1,"image":-1},"c9bdec33-8219-4046-947c-c0d4992537fa","sat-05-files-core","Folders and files collections, with path maintenance and a common-tree trash guard","2026-07-19T00:40:00",{"id":364,"slug":365,"projectId":78,"kind":115,"title":366,"contentHtml":109,"dateLabel":227,"publishedIso":367,"commitUrl":-1,"image":-1},"988e48c8-0129-4b9a-97d0-887c18013c9b","sat-04-settings","A settings page — profile edits, and admin member management","2026-07-18T20:00:00",{"id":369,"slug":370,"projectId":78,"kind":115,"title":371,"contentHtml":109,"dateLabel":227,"publishedIso":372,"commitUrl":-1,"image":-1},"a76ab89b-cdae-4f64-9148-d18bbc59f604","sat-03-foundation","Phase 1 foundation — design tokens, a PocketBase client with an SSR auth cookie, and a global auth guard","2026-07-18T19:30:00",{"id":374,"slug":375,"projectId":78,"kind":115,"title":376,"contentHtml":109,"dateLabel":227,"publishedIso":377,"commitUrl":-1,"image":-1},"0ccee6f7-72e8-4b7c-b887-26c585cc17bb","sat-02-invite-only-users","Invite-only users with admin and member roles, and a guard against self-promotion","2026-07-18T18:45:00",{"id":379,"slug":380,"projectId":78,"kind":127,"title":381,"contentHtml":109,"dateLabel":227,"publishedIso":382,"commitUrl":-1,"image":-1},"59bfaccc-48bc-4848-8fa5-650f4fe519fb","sat-01-project-start","Satchel begins — a self-hosted file store for a small circle, spec'd before a line was written","2026-07-18T17:30:00",[],1787555776558]