[{"data":1,"prerenderedAt":738},["ShallowReactive",2],{"profile":3,"work:vitro":70,"work:neighbours:vitro":485},{"title":4,"description":5,"name":6,"role":7,"tagline":8,"location":9,"country":10,"workEligibility":10,"remote":11,"timezone":12,"email":13,"availability":14,"links":15,"skills":22,"education":40,"languages":52,"slug":54,"body":55},"","I build interfaces for products people use every day — mostly in Vue and Nuxt, and mostly at the\npoint where a design system meets real production constraints.","Jake Armour","Senior Frontend Developer","Vue 3 · Nuxt · TypeScript","Telford, United Kingdom","UK",true,"UTC−2 to UTC+2","jobs@jakearmour.com","Open to senior frontend roles",[16,19],{"label":17,"href":18},"GitHub","https:\u002F\u002Fgithub.com\u002Fjake-armour",{"label":20,"href":21},"LinkedIn","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fjakearmour",{"core":23,"other":31},[24,25,26,27,28,29,30],"Vue 3","Nuxt","TypeScript","JavaScript","HTML\u002FCSS","Tailwind CSS","Pinia",[32,33,34,35,36,37,38,39],"Design systems","Component libraries","Headless CMS","Core Web Vitals","Unit testing","CI\u002FCD","Data visualisation","Git",[41,47],{"institution":42,"qualification":43,"grade":44,"start":45,"end":46},"The University of Huddersfield","MEng Computer Science","Pass with Distinction",2014,2019,{"institution":48,"qualification":49,"grade":50,"start":51,"end":45},"Telford College of Arts and Technology","BTEC Information Technology","D*D*D*",2012,[53],"English (fluent)","profile",{"type":56,"children":57},"root",[58,65],{"type":59,"tag":60,"props":61,"children":62},"element","p",{},[63],{"type":64,"value":5},"text",{"type":59,"tag":60,"props":66,"children":67},{},[68],{"type":64,"value":69},"I'm currently a senior frontend developer at Spamhaus, where I've led two company rebrands and\nbuilt the Vue 3 component library the rest of the team ships on. Outside work I build tools I\nactually need: a component workshop for Vue, a zero-knowledge password manager, and a monitor\nthat watches the containers running in my house.",{"title":71,"description":72,"tagline":73,"year":74,"status":75,"role":76,"featured":11,"order":77,"stack":78,"highlights":82,"slug":86,"body":87},"Vitro","Vitro is a component workshop for Vue 3 — the thing you open to build a component in isolation,\nbefore it has a page to live on.","A Vite-native component workshop for Vue 3",2026,"building","Creator",2,[24,79,26,80,81],"Vite","vue-component-meta","Shiki",[83,84,85],"Stories are real Vue templates","Controls derived from your props","A playground non-developers can use","vitro",{"type":56,"children":88},[89,93,98,111,118,123,136,142,155,345,358,364,392,398,430,435,441,468,474,479],{"type":59,"tag":60,"props":90,"children":91},{},[92],{"type":64,"value":72},{"type":59,"tag":60,"props":94,"children":95},{},[96],{"type":64,"value":97},"It exists because writing Storybook stories was always the part of the job I put off. Authoring\nfriction was the problem I actually wanted to solve; format churn came a close second, because a\nstory format that keeps changing underneath you turns migrations into routine work. Histoire, the\nobvious alternative, had stalled.",{"type":59,"tag":60,"props":99,"children":100},{},[101,103,109],{"type":64,"value":102},"The CLI is ",{"type":59,"tag":104,"props":105,"children":107},"code",{"className":106},[],[108],{"type":64,"value":86},{"type":64,"value":110},". Vite is the builder — not wrapped, not shimmed, just Vite.",{"type":59,"tag":112,"props":113,"children":115},"h2",{"id":114},"who-it-turned-out-to-be-for",[116],{"type":64,"value":117},"Who it turned out to be for",{"type":59,"tag":60,"props":119,"children":120},{},[121],{"type":64,"value":122},"It has earned its keep in a way I did not plan for. A lot of my work at Spamhaus is building\ncomponents that the marketing team have only ever seen as a Figma frame — approved in a design\nfile, never touched. Giving non-technical stakeholders somewhere hands-on to poke at instead\n(change the props yourself, with the Figma frame sitting next to the real rendered component)\nhas been worth more than any amount of written documentation.",{"type":59,"tag":60,"props":124,"children":125},{},[126,128,134],{"type":64,"value":127},"That is what makes the shareable-URL state and the ",{"type":59,"tag":104,"props":129,"children":131},{"className":130},[],[132],{"type":64,"value":133},"figmaUrl",{"type":64,"value":135}," embed the two features I would\nkeep above all the others: they turn \"here is the component\" from a meeting into a link.",{"type":59,"tag":112,"props":137,"children":139},{"id":138},"stories-are-just-vue",[140],{"type":64,"value":141},"Stories are just Vue",{"type":59,"tag":60,"props":143,"children":144},{},[145,147,153],{"type":64,"value":146},"The format is a ",{"type":59,"tag":104,"props":148,"children":150},{"className":149},[],[151],{"type":64,"value":152},".story.vue",{"type":64,"value":154}," file containing ordinary Vue markup:",{"type":59,"tag":156,"props":157,"children":161},"pre",{"className":158,"code":159,"language":160,"meta":4,"style":4},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003CStory title=\"Button\">\n    \u003CVariant title=\"Primary\">\n      \u003CAppButton variant=\"primary\">Save changes\u003C\u002FAppButton>\n    \u003C\u002FVariant>\n  \u003C\u002FStory>\n\u003C\u002Ftemplate>\n","vue",[162],{"type":59,"tag":104,"props":163,"children":164},{"__ignoreMap":4},[165,188,222,253,294,311,328],{"type":59,"tag":166,"props":167,"children":170},"span",{"class":168,"line":169},"line",1,[171,177,183],{"type":59,"tag":166,"props":172,"children":174},{"style":173},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[175],{"type":64,"value":176},"\u003C",{"type":59,"tag":166,"props":178,"children":180},{"style":179},"--shiki-default:#22863A;--shiki-dark:#85E89D",[181],{"type":64,"value":182},"template",{"type":59,"tag":166,"props":184,"children":185},{"style":173},[186],{"type":64,"value":187},">\n",{"type":59,"tag":166,"props":189,"children":190},{"class":168,"line":77},[191,196,201,207,212,218],{"type":59,"tag":166,"props":192,"children":193},{"style":173},[194],{"type":64,"value":195},"  \u003C",{"type":59,"tag":166,"props":197,"children":198},{"style":179},[199],{"type":64,"value":200},"Story",{"type":59,"tag":166,"props":202,"children":204},{"style":203},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[205],{"type":64,"value":206}," title",{"type":59,"tag":166,"props":208,"children":209},{"style":173},[210],{"type":64,"value":211},"=",{"type":59,"tag":166,"props":213,"children":215},{"style":214},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[216],{"type":64,"value":217},"\"Button\"",{"type":59,"tag":166,"props":219,"children":220},{"style":173},[221],{"type":64,"value":187},{"type":59,"tag":166,"props":223,"children":225},{"class":168,"line":224},3,[226,231,236,240,244,249],{"type":59,"tag":166,"props":227,"children":228},{"style":173},[229],{"type":64,"value":230},"    \u003C",{"type":59,"tag":166,"props":232,"children":233},{"style":179},[234],{"type":64,"value":235},"Variant",{"type":59,"tag":166,"props":237,"children":238},{"style":203},[239],{"type":64,"value":206},{"type":59,"tag":166,"props":241,"children":242},{"style":173},[243],{"type":64,"value":211},{"type":59,"tag":166,"props":245,"children":246},{"style":214},[247],{"type":64,"value":248},"\"Primary\"",{"type":59,"tag":166,"props":250,"children":251},{"style":173},[252],{"type":64,"value":187},{"type":59,"tag":166,"props":254,"children":256},{"class":168,"line":255},4,[257,262,267,272,276,281,286,290],{"type":59,"tag":166,"props":258,"children":259},{"style":173},[260],{"type":64,"value":261},"      \u003C",{"type":59,"tag":166,"props":263,"children":264},{"style":179},[265],{"type":64,"value":266},"AppButton",{"type":59,"tag":166,"props":268,"children":269},{"style":203},[270],{"type":64,"value":271}," variant",{"type":59,"tag":166,"props":273,"children":274},{"style":173},[275],{"type":64,"value":211},{"type":59,"tag":166,"props":277,"children":278},{"style":214},[279],{"type":64,"value":280},"\"primary\"",{"type":59,"tag":166,"props":282,"children":283},{"style":173},[284],{"type":64,"value":285},">Save changes\u003C\u002F",{"type":59,"tag":166,"props":287,"children":288},{"style":179},[289],{"type":64,"value":266},{"type":59,"tag":166,"props":291,"children":292},{"style":173},[293],{"type":64,"value":187},{"type":59,"tag":166,"props":295,"children":297},{"class":168,"line":296},5,[298,303,307],{"type":59,"tag":166,"props":299,"children":300},{"style":173},[301],{"type":64,"value":302},"    \u003C\u002F",{"type":59,"tag":166,"props":304,"children":305},{"style":179},[306],{"type":64,"value":235},{"type":59,"tag":166,"props":308,"children":309},{"style":173},[310],{"type":64,"value":187},{"type":59,"tag":166,"props":312,"children":314},{"class":168,"line":313},6,[315,320,324],{"type":59,"tag":166,"props":316,"children":317},{"style":173},[318],{"type":64,"value":319},"  \u003C\u002F",{"type":59,"tag":166,"props":321,"children":322},{"style":179},[323],{"type":64,"value":200},{"type":59,"tag":166,"props":325,"children":326},{"style":173},[327],{"type":64,"value":187},{"type":59,"tag":166,"props":329,"children":331},{"class":168,"line":330},7,[332,337,341],{"type":59,"tag":166,"props":333,"children":334},{"style":173},[335],{"type":64,"value":336},"\u003C\u002F",{"type":59,"tag":166,"props":338,"children":339},{"style":179},[340],{"type":64,"value":182},{"type":59,"tag":166,"props":342,"children":343},{"style":173},[344],{"type":64,"value":187},{"type":59,"tag":60,"props":346,"children":347},{},[348,350,356],{"type":64,"value":349},"No abstract ",{"type":59,"tag":104,"props":351,"children":353},{"className":352},[],[354],{"type":64,"value":355},"args",{"type":64,"value":357}," object standing in for a template. You write the markup you would actually\nwrite, which means a story is readable by anyone on the team and stays honest about how the\ncomponent is really used. The format is a semver-protected, additive-only contract — the\nmigration churn is the thing I was trying to escape, so committing to never causing it was the\npoint.",{"type":59,"tag":112,"props":359,"children":361},{"id":360},"controls-you-didnt-have-to-write",[362],{"type":64,"value":363},"Controls you didn't have to write",{"type":59,"tag":60,"props":365,"children":366},{},[367,369,374,376,382,384,390],{"type":64,"value":368},"Controls are derived from the component's own props via ",{"type":59,"tag":104,"props":370,"children":372},{"className":371},[],[373],{"type":64,"value":80},{"type":64,"value":375},". A variant with no\nslot content auto-renders ",{"type":59,"tag":104,"props":377,"children":379},{"className":378},[],[380],{"type":64,"value":381},"\u003CComponent v-bind=\"state\" \u002F>",{"type":64,"value":383},", so a component you have just written\ngets a working playground with zero configuration. Manual ",{"type":59,"tag":104,"props":385,"children":387},{"className":386},[],[388],{"type":64,"value":389},"Hst*",{"type":64,"value":391}," controls are there when you want\nto override, and control changes update the preview live without remounting — state survives, so\nyou can tweak a prop mid-interaction.",{"type":59,"tag":112,"props":393,"children":395},{"id":394},"three-views",[396],{"type":64,"value":397},"Three views",{"type":59,"tag":60,"props":399,"children":400},{},[401,407,409,414,416,421,423,428],{"type":59,"tag":402,"props":403,"children":404},"strong",{},[405],{"type":64,"value":406},"Canvas",{"type":64,"value":408}," for the component, ",{"type":59,"tag":402,"props":410,"children":411},{},[412],{"type":64,"value":413},"Source",{"type":64,"value":415}," for Shiki-highlighted markup, ",{"type":59,"tag":402,"props":417,"children":418},{},[419],{"type":64,"value":420},"Docs",{"type":64,"value":422}," for autodocs —\nprop, event and slot tables pulled from the type signatures, including JSDoc descriptions. If a\n",{"type":59,"tag":104,"props":424,"children":426},{"className":425},[],[427],{"type":64,"value":133},{"type":64,"value":429}," is set on the story, the Figma frame embeds alongside it.",{"type":59,"tag":60,"props":431,"children":432},{},[433],{"type":64,"value":434},"The full UI state — story, variant, every control value, viewport, background, theme — is encoded\nin the URL. Any view you are looking at is a link you can paste to a designer.",{"type":59,"tag":112,"props":436,"children":438},{"id":437},"built-output",[439],{"type":64,"value":440},"Built output",{"type":59,"tag":60,"props":442,"children":443},{},[444,450,452,458,460,466],{"type":59,"tag":104,"props":445,"children":447},{"className":446},[],[448],{"type":64,"value":449},"vitro build",{"type":64,"value":451}," produces a static, relative-path ",{"type":59,"tag":104,"props":453,"children":455},{"className":454},[],[456],{"type":64,"value":457},"dist\u002F",{"type":64,"value":459}," with per-story chunks that will serve from\nany subpath. It also emits a Storybook-compatible ",{"type":59,"tag":104,"props":461,"children":463},{"className":462},[],[464],{"type":64,"value":465},"\u002Findex.json",{"type":64,"value":467},", so existing visual-regression\ntooling — Lost Pixel, Playwright — works against it without adaptation. Adopting a new workshop\nshould not mean rewriting your VR pipeline.",{"type":59,"tag":112,"props":469,"children":471},{"id":470},"status",[472],{"type":64,"value":473},"Status",{"type":59,"tag":60,"props":475,"children":476},{},[477],{"type":64,"value":478},"M1–M6 complete: the full v1 workshop is working. Next is hosted publishing.",{"type":59,"tag":480,"props":481,"children":482},"style",{},[483],{"type":64,"value":484},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"previous":486,"next":630},{"title":487,"description":488,"tagline":489,"year":490,"status":491,"role":492,"featured":11,"order":169,"stack":493,"links":499,"highlights":503,"slug":507,"body":508},"Jungleland","Jungleland is a children's play centre in Telford. Until 2021 it had no booking system at all —\nfamilies simply turned up. Social distancing ended that: entry had to be capped per session, so\nthe whole thing had to be built from nothing. Timed slots with hard capacity limits, a deposit\ntaken at the point of booking, and a way for staff to open and close dates themselves without\ncalling me.","Timed-slot booking and deposits for a children's play centre",2021,"live","Sole developer — frontend, payments, backend, deployment",[494,30,495,496,497,498,29],"Nuxt 4","Stripe","AWS Amplify","AppSync \u002F DynamoDB","AWS SES",[500],{"label":501,"href":502,"primary":11},"junglelandbooking.com","https:\u002F\u002Fjunglelandbooking.com",[504,505,506],"Real customers, real payments","Multi-step flow that survives a refresh","Staff-managed dates and capacity","jungleland",{"type":56,"children":509},[510,514,519,525,530,540,559,565,585,591,608,614,619,625],{"type":59,"tag":60,"props":511,"children":512},{},[513],{"type":64,"value":488},{"type":59,"tag":60,"props":515,"children":516},{},[517],{"type":64,"value":518},"It has been running and taking bookings ever since.",{"type":59,"tag":112,"props":520,"children":522},{"id":521},"the-booking-flow",[523],{"type":64,"value":524},"The booking flow",{"type":59,"tag":60,"props":526,"children":527},{},[528],{"type":64,"value":529},"A parent booking a party is usually doing it one-handed. The flow is deliberately linear — pick a\ndate, pick a slot, pay a deposit, get a confirmation — and each step is its own route:",{"type":59,"tag":156,"props":531,"children":535},{"className":532,"code":534,"language":64},[533],"language-text","\u002Fbook\n  → \u002Fbook\u002F[date]\n    → \u002Fbook\u002F[date]\u002F[timeslot]\n      → \u002Fdeposit\u002F[id]\n        → \u002Fconfirmation\u002F[id]\n",[536],{"type":59,"tag":104,"props":537,"children":538},{"__ignoreMap":4},[539],{"type":64,"value":534},{"type":59,"tag":60,"props":541,"children":542},{},[543,545,549,551,557],{"type":64,"value":544},"Booking state lives in a ",{"type":59,"tag":402,"props":546,"children":547},{},[548],{"type":64,"value":30},{"type":64,"value":550}," store persisted to ",{"type":59,"tag":104,"props":552,"children":554},{"className":553},[],[555],{"type":64,"value":556},"localStorage",{"type":64,"value":558},", so a half-finished booking\nsurvives a refresh, a backgrounded tab, or a phone that decides to lock mid-payment. That single\ndecision removed the most common support complaint from the first version.",{"type":59,"tag":112,"props":560,"children":562},{"id":561},"capacity-is-the-hard-part",[563],{"type":64,"value":564},"Capacity is the hard part",{"type":59,"tag":60,"props":566,"children":567},{},[568,570,576,578,583],{"type":64,"value":569},"Everything else follows from not overselling a slot. The data model is deliberately narrow —\n",{"type":59,"tag":104,"props":571,"children":573},{"className":572},[],[574],{"type":64,"value":575},"Date → TimeSlot → Booking",{"type":64,"value":577}," in ",{"type":59,"tag":402,"props":579,"children":580},{},[581],{"type":64,"value":582},"AppSync\u002FGraphQL over DynamoDB",{"type":64,"value":584}," — with capacity held on the\ntimeslot rather than derived at read time. Abandoned bookings expire on their own through\nDynamoDB TTLs rather than needing a cleanup job, so a parent who bails at the payment step\nreleases their slot without anyone intervening.",{"type":59,"tag":112,"props":586,"children":588},{"id":587},"payments-and-mail",[589],{"type":64,"value":590},"Payments and mail",{"type":59,"tag":60,"props":592,"children":593},{},[594,596,600,602,606],{"type":64,"value":595},"Deposits go through ",{"type":59,"tag":402,"props":597,"children":598},{},[599],{"type":64,"value":495},{"type":64,"value":601},", with the checkout session created server-side in a Nitro route so\nno key is ever exposed to the client. Confirmations are sent through ",{"type":59,"tag":402,"props":603,"children":604},{},[605],{"type":64,"value":498},{"type":64,"value":607},". Both are\nboring, on purpose — this is somebody's actual revenue.",{"type":59,"tag":112,"props":609,"children":611},{"id":610},"admin",[612],{"type":64,"value":613},"Admin",{"type":59,"tag":60,"props":615,"children":616},{},[617],{"type":64,"value":618},"Staff get a JWT-authenticated admin area to create bookable dates, set slot capacity, and see\nwhat has been booked. It is intentionally plain: the people using it are running a play centre,\nnot learning software.",{"type":59,"tag":112,"props":620,"children":622},{"id":621},"what-id-change",[623],{"type":64,"value":624},"What I'd change",{"type":59,"tag":60,"props":626,"children":627},{},[628],{"type":64,"value":629},"The repo has no automated tests and no linting — it grew from something built quickly under a\nreopening deadline and never had that debt paid down. If I picked it up again today, capacity\nlogic is the first thing I'd put under test, because it is the part that costs real money when\nit is wrong.",{"title":631,"description":632,"tagline":633,"year":74,"status":75,"role":76,"featured":11,"order":224,"stack":634,"highlights":639,"slug":643,"body":644},"Cubby","Cubby is a self-hosted password manager — a Bitwarden replacement with a web dashboard and a\nFirefox extension. The constraint that shapes every decision in it: the server never sees\nanything it could decrypt. Your master password and vault keys do not leave the client.","A zero-knowledge password manager",[494,26,635,636,637,638],"Web Crypto","Postgres","Docker","WXT",[640,641,642],"Server only ever holds ciphertext","Shared vaults without breaking the model","Proven by test, not by claim","cubby",{"type":56,"children":645},[646,658,663,669,689,695,700,706,711,717],{"type":59,"tag":60,"props":647,"children":648},{},[649,651,656],{"type":64,"value":650},"Cubby is a self-hosted password manager — a Bitwarden replacement with a web dashboard and a\nFirefox extension. The constraint that shapes every decision in it: ",{"type":59,"tag":402,"props":652,"children":653},{},[654],{"type":64,"value":655},"the server never sees\nanything it could decrypt.",{"type":64,"value":657}," Your master password and vault keys do not leave the client.",{"type":59,"tag":60,"props":659,"children":660},{},[661],{"type":64,"value":662},"That is easy to claim and easy to get subtly wrong, so it is asserted by a test that talks to a\nlive Postgres, registers a user, creates an item, and fails if any row holds anything but\nciphertext. The property is enforced by CI rather than by my good intentions.",{"type":59,"tag":112,"props":664,"children":666},{"id":665},"where-the-crypto-lives",[667],{"type":64,"value":668},"Where the crypto lives",{"type":59,"tag":60,"props":670,"children":671},{},[672,674,680,682,687],{"type":64,"value":673},"Key derivation happens in the browser. The symmetric key stays in memory only — reload the page\nand you get the unlock screen back, master password required. The API deals exclusively in opaque\nblobs; it can tell you ",{"type":59,"tag":675,"props":676,"children":677},"em",{},[678],{"type":64,"value":679},"that",{"type":64,"value":681}," an item changed and ",{"type":59,"tag":675,"props":683,"children":684},{},[685],{"type":64,"value":686},"when",{"type":64,"value":688},", never what it says.",{"type":59,"tag":112,"props":690,"children":692},{"id":691},"sharing-without-cheating",[693],{"type":64,"value":694},"Sharing without cheating",{"type":59,"tag":60,"props":696,"children":697},{},[698],{"type":64,"value":699},"Shared vaults are where zero-knowledge designs usually quietly compromise. Cubby gives every user\nan RSA key-pair, with the private key wrapped under their own symmetric key. A shared vault has\nits own key, delivered to each member encrypted to that member's public key. Invite by email,\naccept, and everyone can read and edit — while the server still holds nothing but ciphertext and\na set of per-member wrapped keys it cannot open.",{"type":59,"tag":112,"props":701,"children":703},{"id":702},"the-rest-of-it",[704],{"type":64,"value":705},"The rest of it",{"type":59,"tag":60,"props":707,"children":708},{},[709],{"type":64,"value":710},"Revision-based delta sync against an encrypted local cache, so a client fetches what changed\nrather than the whole vault. Folders with encrypted names. Live TOTP codes. A configurable\ngenerator with look-alike exclusion. Client-side Bitwarden JSON import — decrypted, re-encrypted\nand uploaded without the plaintext touching the wire. Account recovery via a one-time key issued\nat signup, because \"we can reset it for you\" is precisely what a zero-knowledge system cannot\noffer.",{"type":59,"tag":112,"props":712,"children":714},{"id":713},"running-it",[715],{"type":64,"value":716},"Running it",{"type":59,"tag":60,"props":718,"children":719},{},[720,722,728,730,736],{"type":64,"value":721},"One ",{"type":59,"tag":104,"props":723,"children":725},{"className":724},[],[726],{"type":64,"value":727},"docker compose up",{"type":64,"value":729}," brings up Postgres, the API, and a Caddy proxy handling auto-TLS and\nrouting, plus a scheduled ",{"type":59,"tag":104,"props":731,"children":733},{"className":732},[],[734],{"type":64,"value":735},"pg_dump",{"type":64,"value":737}," backup with retention. The Firefox extension is built with\nWXT and is the piece still in progress.",1785686103713]