OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop commits / 925c749

migrate browser catalogue away from remote pear links
925c7493f38287efa3d883a913a468b410dafa46
by bigdestiny2 <33146784+bigdestiny2@users.noreply.github.com> · 2026-07-22T23:20:56.000Z

Changed files (6) +87 -42

backend/catalog-safety.cjs +25 -2
……@@ -28,6 +28,7 @@ const HEX64_RE = /^[0-9a-f]{64}$/i
2828const Z32_RE = /^[13-9a-km-uw-z]{52}$/i
2929const APP_LINK_RE = /^(?:hyper|pear|file):\/\/.+/i
3030const VERIFICATION_RANK = { 'author-signed': 3, 'relay-listed': 2, unverified: 1 }
31const NATIVE_DELIVERY_STATUSES = new Set(['migration-required', 'available'])
3132
3233function trimString (value) {
3334 return typeof value === 'string' ? value.trim() : ''
……@@ -90,6 +91,16 @@ function normalizeAppType (value) {
9091 return type === 'standalone' || type === 'hypersite' ? type : ''
9192}
9293
94function normalizeLegacyMigrationId (value) {
95 const id = trimString(value).toLowerCase()
96 return Z32_RE.test(id) ? id : ''
97}
98
99function normalizeNativeDelivery (value) {
100 const status = trimString(value && typeof value === 'object' ? value.status : value)
101 return NATIVE_DELIVERY_STATUSES.has(status) ? { status } : null
102}
103
93104function normalizeCatalogApp (app, opts = {}) {
94105 if (!app || typeof app !== 'object' || Array.isArray(app)) return null
95106 const out = { ...app }
……@@ -102,8 +113,10 @@ function normalizeCatalogApp (app, opts = {}) {
102113 const rawLink = trimString(out.link)
103114 const link = normalizeCatalogLink(out.link)
104115 const driveKey = normalizeDriveKey(key) || driveKeyFromHyperLink(link)
105 const id = trimString(out.id) || driveKey || link
106 if ((key && !driveKey && !link) || (rawLink && !link && !driveKey) || (!driveKey && !link) || !id) return null
116 const legacyMigrationId = normalizeLegacyMigrationId(out.legacyMigrationId)
117 const nativeDelivery = normalizeNativeDelivery(out.nativeDelivery)
118 const id = trimString(out.id) || driveKey || link || (legacyMigrationId ? `legacy:${legacyMigrationId}` : '')
119 if ((key && !driveKey && !link) || (rawLink && !link && !driveKey) || (!driveKey && !link && !legacyMigrationId) || !id) return null
107120
108121 out.id = id || undefined
109122 out.name = trimString(out.name) || id || undefined
……@@ -111,6 +124,10 @@ function normalizeCatalogApp (app, opts = {}) {
111124 else delete out.driveKey
112125 if (link) out.link = link
113126 else delete out.link
127 if (legacyMigrationId) out.legacyMigrationId = legacyMigrationId
128 else delete out.legacyMigrationId
129 if (nativeDelivery) out.nativeDelivery = nativeDelivery
130 else delete out.nativeDelivery
114131 const type = normalizeAppType(out.type)
115132 if (type) out.type = type
116133 else delete out.type
……@@ -145,6 +162,8 @@ function catalogAppStableKey (app) {
145162 const driveKey = normalizeDriveKey(key) || driveKeyFromHyperLink(link)
146163 if (driveKey) return `drive:${driveKey}`
147164 if (link) return `link:${link}`
165 const legacyMigrationId = normalizeLegacyMigrationId(app.legacyMigrationId)
166 if (legacyMigrationId) return `legacy:${legacyMigrationId}`
148167 const id = trimString(app.id)
149168 if (id) return `id:${id}`
150169 return ''
……@@ -233,6 +252,8 @@ function catalogAppSearchText (app) {
233252 app.source,
234253 app.catalogName,
235254 app.verification,
255 app.legacyMigrationId,
256 app.nativeDelivery && app.nativeDelivery.status,
236257 app.link,
237258 app.driveKey,
238259 ...(Array.isArray(app.categories) ? app.categories : []),
……@@ -293,6 +314,8 @@ module.exports = {
293314 normalizeDriveKey,
294315 driveKeyFromHyperLink,
295316 normalizeCatalogData,
317 normalizeLegacyMigrationId,
318 normalizeNativeDelivery,
296319 normalizeAppType,
297320 scrubPrototypeKeys,
298321 safeJSONParse,
backend/catalogue-seed.js +29 -16
……@@ -18,7 +18,9 @@ module.exports = {
1818 "name": "PearBrowser Desktop",
1919 "type": "standalone",
2020 "driveKey": "03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f",
21 "link": "pear://tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty",
21 "link": "hyper://03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f/",
22 "legacyMigrationId": "tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty",
23 "nativeDeliveryStatus": "migration-required",
2224 "author": "bigdestiny2",
2325 "categories": [
2426 "browser",
……@@ -90,7 +92,9 @@ module.exports = {
9092 "name": "Pear Dealroom",
9193 "type": "standalone",
9294 "driveKey": "0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90",
93 "link": "pear://7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o",
95 "link": "hyper://0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90/",
96 "legacyMigrationId": "7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o",
97 "nativeDeliveryStatus": "migration-required",
9498 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTJsMTYgNnYxMWMwIDExLTcgMTgtMTYgMjMtOS01LTE2LTEyLTE2LTIzVjE4eiIgZmlsbD0iIzU4YTZmZiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI2IDI4aDEyTTI2IDM0aDEyTTI2IDQwaDgiLz48L2c+PC9zdmc+",
9599 "author": "bigdestiny2",
96100 "categories": [
……@@ -108,7 +112,9 @@ module.exports = {
108112 "name": "Paste",
109113 "type": "standalone",
110114 "driveKey": "25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b",
111 "link": "pear://qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo",
115 "link": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/",
116 "legacyMigrationId": "qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo",
117 "nativeDeliveryStatus": "migration-required",
112118 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMTYiIHdpZHRoPSIyOCIgaGVpZ2h0PSIzNCIgcng9IjQiIGZpbGw9IiM0YWRlODAiLz48cmVjdCB4PSIyNSIgeT0iMTEiIHdpZHRoPSIxNCIgaGVpZ2h0PSI5IiByeD0iMi41IiBmaWxsPSIjNGFkZTgwIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI0IDMwaDE2TTI0IDM2aDE2TTI0IDQyaDEwIi8+PC9nPjwvc3ZnPg==",
113119 "author": "bigdestiny2",
114120 "categories": [
……@@ -125,9 +131,10 @@ module.exports = {
125131 },
126132 {
127133 "name": "PearPoker",
128 "type": "standalone",
134 "type": "hypersite",
129135 "driveKey": "850929ab0b7f1eb927dd69c6ae057af0a43fba1ced4c33e0df2e7cff0ee92268",
130 "link": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy",
136 "legacyMigrationId": "owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy",
137 "nativeDeliveryStatus": "migration-required",
131138 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTNDMjEgMjUgMTUgMjkgMTUgMzdhOCA4IDAgMCAwIDE0IDVjLTEgNC0zIDYtNiA5aDE4Yy0zLTMtNS01LTYtOWE4IDggMCAwIDAgMTQtNWMwLTgtNi0xMi0xNy0yNHoiIGZpbGw9IiMzZmI5NTAiLz48L3N2Zz4=",
132139 "author": "bigdestiny2",
133140 "categories": [
……@@ -142,8 +149,9 @@ module.exports = {
142149 },
143150 {
144151 "name": "Keet",
145 "type": "standalone",
146 "link": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo",
152 "type": "hypersite",
153 "legacyMigrationId": "oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo",
154 "nativeDeliveryStatus": "migration-required",
147155 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiNmYmJmMjQiLz48ZyBmaWxsPSIjMGEwZDEyIj48Y2lyY2xlIGN4PSIyNSIgY3k9IjI4LjUiIHI9IjIuNiIvPjxjaXJjbGUgY3g9IjMzIiBjeT0iMjguNSIgcj0iMi42Ii8+PGNpcmNsZSBjeD0iNDEiIGN5PSIyOC41IiByPSIyLjYiLz48L2c+PC9zdmc+",
148156 "author": "Holepunch",
149157 "categories": [
……@@ -157,8 +165,9 @@ module.exports = {
157165 },
158166 {
159167 "name": "PearPass",
160 "type": "standalone",
161 "link": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo",
168 "type": "hypersite",
169 "legacyMigrationId": "tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo",
170 "nativeDeliveryStatus": "migration-required",
162171 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMzAiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyMiIgcng9IjQiIGZpbGw9IiMzZmI5NTAiLz48cGF0aCBkPSJNMjQgMzB2LTZhOCA4IDAgMCAxIDE2IDB2NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjM2ZiOTUwIiBzdHJva2Utd2lkdGg9IjQiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjM5IiByPSIzLjQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIzMC41IiB5PSIzOSIgd2lkdGg9IjMiIGhlaWdodD0iOCIgcng9IjEuNSIgZmlsbD0iIzBhMGQxMiIvPjwvc3ZnPg==",
163172 "author": "Tether",
164173 "categories": [
……@@ -173,7 +182,8 @@ module.exports = {
173182 {
174183 "name": "Peercord",
175184 "type": "standalone",
176 "link": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy",
185 "legacyMigrationId": "wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy",
186 "nativeDeliveryStatus": "migration-required",
177187 "author": "Mastercodeon",
178188 "categories": [
179189 "chat",
……@@ -204,8 +214,9 @@ module.exports = {
204214 },
205215 {
206216 "name": "anonGPT",
207 "type": "standalone",
208 "link": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo",
217 "type": "hypersite",
218 "legacyMigrationId": "rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo",
219 "nativeDeliveryStatus": "migration-required",
209220 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiMyMmQzZWUiLz48cGF0aCBkPSJNMzMgMjBsMi4zIDUuNyA1LjcgMi4zLTUuNyAyLjMtMi4zIDUuNy0yLjMtNS43LTUuNy0yLjMgNS43LTIuM3oiIGZpbGw9IiMwYTBkMTIiLz48L3N2Zz4=",
210221 "author": "anonGPT",
211222 "categories": [
……@@ -218,9 +229,10 @@ module.exports = {
218229 },
219230 {
220231 "name": "Pear POS",
221 "type": "standalone",
232 "type": "hypersite",
222233 "driveKey": "b776f15f3e6860ecf6d923853c295350e55b708772e67d7124899b96aecfcd43",
223 "link": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy",
234 "legacyMigrationId": "myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy",
235 "nativeDeliveryStatus": "migration-required",
224236 "author": "Pear POS",
225237 "categories": [
226238 "business",
……@@ -233,9 +245,10 @@ module.exports = {
233245 },
234246 {
235247 "name": "Pear Tickets",
236 "type": "standalone",
248 "type": "hypersite",
237249 "driveKey": "ec309f516da659718746fe10ded086e2b6d157718c3e3651f86e07a4df34210a",
238 "link": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy",
250 "legacyMigrationId": "gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy",
251 "nativeDeliveryStatus": "migration-required",
239252 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjRoMzZhNCA0IDAgMCAxIDQgNHYxLjVhMy4yIDMuMiAwIDAgMCAwIDYuNFYzN2E0IDQgMCAwIDEtNCA0SDE0YTQgNCAwIDAgMS00LTR2LTEuMWEzLjIgMy4yIDAgMCAwIDAtNi40VjI4YTQgNCAwIDAgMSA0LTR6IiBmaWxsPSIjZmJiZjI0Ii8+PHBhdGggZD0iTTQwIDI2djEzIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMi40IiBzdHJva2UtZGFzaGFycmF5PSIzIDMiLz48L3N2Zz4=",
240253 "author": "bigdestiny2",
241254 "categories": [
backend/sheets-catalog.js +6 -2
……@@ -21,7 +21,7 @@ const b4a = require('b4a')
2121const { normalizeCatalogApp } = require('./catalog-safety.cjs')
2222
2323// The canonical `apps` schema (design §4.1). Created on a fresh room; carries
24// both driveKey (installable) and link (launch-only), plus the run-in-tab type.
24// browser-drive metadata and native migration state, plus the run-in-tab type.
2525const APPS_SCHEMA = {
2626 type: 'object',
2727 properties: {
……@@ -30,6 +30,8 @@ const APPS_SCHEMA = {
3030 description: { type: 'string', maxLength: 1000 },
3131 driveKey: { type: 'string', pattern: '^[0-9a-f]{64}$' },
3232 link: { type: 'string', maxLength: 300 },
33 legacyMigrationId: { type: 'string', pattern: '^[13-9a-km-uw-z]{52}$' },
34 nativeDeliveryStatus: { enum: ['migration-required', 'available'] },
3335 iconData: { type: 'string', maxLength: 20000 },
3436 type: { enum: ['standalone', 'hypersite'] },
3537 author: { type: 'string', maxLength: 200 },
……@@ -43,7 +45,7 @@ const APPS_SCHEMA = {
4345 verification: { enum: ['unverified', 'relay-listed', 'author-signed'] }
4446 },
4547 required: ['name', 'type'],
46 anyOf: [{ required: ['driveKey'] }, { required: ['link'] }],
48 anyOf: [{ required: ['driveKey'] }, { required: ['link'] }, { required: ['legacyMigrationId'] }],
4749 additionalProperties: false
4850}
4951
……@@ -114,6 +116,8 @@ function rowToApp (row) {
114116 description: j.description,
115117 driveKey: j.driveKey || null,
116118 link: j.link || null,
119 legacyMigrationId: j.legacyMigrationId || null,
120 nativeDelivery: j.nativeDeliveryStatus ? { status: j.nativeDeliveryStatus } : null,
117121 type: j.type || (j.link && !j.driveKey ? 'standalone' : undefined),
118122 author: j.author || null,
119123 homepage: j.homepage || null,
catalog-source/pearbrowser-network.catalog.json +17 -17
……@@ -17,7 +17,7 @@
1717 "p2p"
1818 ],
1919 "publishedAt": "2026-04-28T00:00:00Z",
20 "legacyPearLink": "pear://tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty",
20 "legacyMigrationId": "tco5k7h38uoxatedp1wongdbhjxow1x7jiwm3t1i9cujbebhsbty",
2121 "nativeDelivery": { "status": "migration-required" },
2222 "driveKey": "03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f",
2323 "homepage": "hyper://03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f/",
……@@ -116,7 +116,7 @@
116116 ],
117117 "verification": "relay-listed",
118118 "link": "hyper://0724aabf2ad6394983f91c6b24ebd417cb3d25addcf29c98eb246c512dc77f90/",
119 "legacyPearLink": "pear://7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o",
119 "legacyMigrationId": "7octu5dimjye8rn68raehwuetk5mttzt4zxh6yjw1eqktn49bq7o",
120120 "nativeDelivery": { "status": "migration-required" },
121121 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMzIgMTJsMTYgNnYxMWMwIDExLTcgMTgtMTYgMjMtOS01LTE2LTEyLTE2LTIzVjE4eiIgZmlsbD0iIzU4YTZmZiIvPjxnIHN0cm9rZT0iIzBhMGQxMiIgc3Ryb2tlLXdpZHRoPSIyLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTI2IDI4aDEyTTI2IDM0aDEyTTI2IDQwaDgiLz48L2c+PC9zdmc+"
122122 },
……@@ -134,7 +134,7 @@
134134 "publishedAt": "2026-06-21T00:00:00Z",
135135 "type": "standalone",
136136 "link": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/",
137 "legacyPearLink": "pear://qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo",
137 "legacyMigrationId": "qnax5k8ojtod51ci9qwkrawdof1hx5w3a7gqbueoqnzzq9dw5hfo",
138138 "nativeDelivery": { "status": "migration-required" },
139139 "homepage": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/",
140140 "url": "hyper://25a06bb3dddec8138e9eda606cc4a11e9ebbe47815fd5d22064b30cff752bb5b/",
……@@ -161,8 +161,8 @@
161161 "p2p"
162162 ],
163163 "publishedAt": "2026-06-21T00:00:00Z",
164 "pearLink": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy",
165 "link": "pear://owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy",
164 "legacyMigrationId": "owr1ukamxhxm1j67p8dkhbm46n1d9qoh7igd8ag9f36x6dzjrjwy",
165 "nativeDelivery": { "status": "migration-required" },
166166 "driveKey": "850929ab0b7f1eb927dd69c6ae057af0a43fba1ced4c33e0df2e7cff0ee92268",
167167 "type": "pear-app",
168168 "license": "MIT",
……@@ -187,8 +187,8 @@
187187 "version": "1.0.0",
188188 "publishedAt": "2026-06-21T00:00:00Z",
189189 "verification": "relay-listed",
190 "link": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo",
191 "pearLink": "pear://oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo",
190 "legacyMigrationId": "oeeoz3w6fjjt7bym3ndpa6hhicm8f8naxyk11z4iypeoupn6jzpo",
191 "nativeDelivery": { "status": "migration-required" },
192192 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiNmYmJmMjQiLz48ZyBmaWxsPSIjMGEwZDEyIj48Y2lyY2xlIGN4PSIyNSIgY3k9IjI4LjUiIHI9IjIuNiIvPjxjaXJjbGUgY3g9IjMzIiBjeT0iMjguNSIgcj0iMi42Ii8+PGNpcmNsZSBjeD0iNDEiIGN5PSIyOC41IiByPSIyLjYiLz48L2c+PC9zdmc+"
193193 },
194194 {
……@@ -204,15 +204,15 @@
204204 "version": "1.0.0",
205205 "publishedAt": "2026-06-21T00:00:00Z",
206206 "verification": "relay-listed",
207 "link": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo",
208 "pearLink": "pear://tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo",
207 "legacyMigrationId": "tywsat7gz8m65ejx4zjn3773pbdc4j8m66tukis8dgzekraymtzo",
208 "nativeDelivery": { "status": "migration-required" },
209209 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIxOCIgeT0iMzAiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyMiIgcng9IjQiIGZpbGw9IiMzZmI5NTAiLz48cGF0aCBkPSJNMjQgMzB2LTZhOCA4IDAgMCAxIDE2IDB2NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjM2ZiOTUwIiBzdHJva2Utd2lkdGg9IjQiLz48Y2lyY2xlIGN4PSIzMiIgY3k9IjM5IiByPSIzLjQiIGZpbGw9IiMwYTBkMTIiLz48cmVjdCB4PSIzMC41IiB5PSIzOSIgd2lkdGg9IjMiIGhlaWdodD0iOCIgcng9IjEuNSIgZmlsbD0iIzBhMGQxMiIvPjwvc3ZnPg=="
210210 },
211211 {
212212 "id": "peercord",
213213 "name": "Peercord",
214214 "type": "standalone",
215 "link": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy",
215 "legacyMigrationId": "wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy",
216216 "author": "Mastercodeon",
217217 "categories": [
218218 "chat",
……@@ -226,7 +226,7 @@
226226 "homepage": "https://git.churchofmalware.org/mastercodeon/Peercord",
227227 "source": "https://git.churchofmalware.org/mastercodeon/Peercord",
228228 "license": "GPL-3.0",
229 "pearLink": "pear://wmir47w7mai3b1skj66mx7fzso6k6o91kipaney7gtt69npimouy",
229 "nativeDelivery": { "status": "migration-required" },
230230 "publishedAt": "2026-06-22T00:00:00Z"
231231 },
232232 {
……@@ -257,8 +257,8 @@
257257 "version": "1.0.0",
258258 "publishedAt": "2026-06-21T00:00:00Z",
259259 "verification": "relay-listed",
260 "link": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo",
261 "pearLink": "pear://rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo",
260 "legacyMigrationId": "rpzh3fsgg38kfir9nmae7x3o8ubofddzzixr5js4mxd6a6drb6wo",
261 "nativeDelivery": { "status": "migration-required" },
262262 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjFhNiA2IDAgMCAxIDYtNmgyNGE2IDYgMCAwIDEgNiA2djE1YTYgNiAwIDAgMS02IDZIMjlsLTEwIDh2LThhNiA2IDAgMCAxLTUtNnoiIGZpbGw9IiMyMmQzZWUiLz48cGF0aCBkPSJNMzMgMjBsMi4zIDUuNyA1LjcgMi4zLTUuNyAyLjMtMi4zIDUuNy0yLjMtNS43LTUuNy0yLjMgNS43LTIuM3oiIGZpbGw9IiMwYTBkMTIiLz48L3N2Zz4="
263263 },
264264 {
……@@ -275,8 +275,8 @@
275275 "publishedAt": "2026-06-21T00:00:00Z",
276276 "verification": "relay-listed",
277277 "driveKey": "b776f15f3e6860ecf6d923853c295350e55b708772e67d7124899b96aecfcd43",
278 "link": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy",
279 "pearLink": "pear://myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy"
278 "legacyMigrationId": "myhk94sz7tokqcs58173xe7359c6nd9enwrm7z7xrthy9xsr7ehy",
279 "nativeDelivery": { "status": "migration-required" }
280280 },
281281 {
282282 "id": "pear-tickets",
……@@ -291,8 +291,8 @@
291291 "publishedAt": "2026-06-21T00:00:00Z",
292292 "verification": "relay-listed",
293293 "driveKey": "ec309f516da659718746fe10ded086e2b6d157718c3e3651f86e07a4df34210a",
294 "link": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy",
295 "pearLink": "pear://gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy",
294 "legacyMigrationId": "gsnmwo4kdopbcif44wgt9k1ysmwwn1erh4o1358kis1ebtwbpouy",
295 "nativeDelivery": { "status": "migration-required" },
296296 "iconData": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMwYTBkMTIiLz48cGF0aCBkPSJNMTQgMjRoMzZhNCA0IDAgMCAxIDQgNHYxLjVhMy4yIDMuMiAwIDAgMCAwIDYuNFYzN2E0IDQgMCAwIDEtNCA0SDE0YTQgNCAwIDAgMS00LTR2LTEuMWEzLjIgMy4yIDAgMCAwIDAtNi40VjI4YTQgNCAwIDAgMSA0LTR6IiBmaWxsPSIjZmJiZjI0Ii8+PHBhdGggZD0iTTQwIDI2djEzIiBzdHJva2U9IiMwYTBkMTIiIHN0cm9rZS13aWR0aD0iMi40IiBzdHJva2UtZGFzaGFycmF5PSIzIDMiLz48L3N2Zz4="
297297 }
298298 ]
scripts/gen-catalogue-seed.mjs +5 -2
……@@ -14,7 +14,8 @@
1414// The manifest is rich (id, pearLink, homepage, source, license, platforms, …);
1515// seed rows are validated against the strict APPS_SCHEMA
1616// (additionalProperties:false), so we project onto exactly its allowed fields and
17// infer `type` from `link`.
17// infer `type` from `link`. Native migration records retain only a local
18// migration ID, never an executable remote URI.
1819
1920import { readFileSync, writeFileSync } from 'node:fs'
2021import { fileURLToPath } from 'node:url'
……@@ -52,6 +53,8 @@ function toSeedRow (a) {
5253 const row = { name: a.name, type: manifestType(a.type) || (a.link ? 'standalone' : 'hypersite') }
5354 if (a.driveKey && /^[0-9a-f]{64}$/i.test(a.driveKey)) row.driveKey = a.driveKey
5455 if (a.link) row.link = a.link
56 if (typeof a.legacyMigrationId === 'string') row.legacyMigrationId = a.legacyMigrationId
57 if (a.nativeDelivery && typeof a.nativeDelivery.status === 'string') row.nativeDeliveryStatus = a.nativeDelivery.status
5558 // Inline icon (data: URI) for apps without a fetchable drive icon — rendered
5659 // directly by AppIcon, so every catalogue app shows a real icon, not a glyph.
5760 if (a.iconData) row.iconData = a.iconData
……@@ -69,7 +72,7 @@ function toSeedRow (a) {
6972const rows = cat.apps.map(toSeedRow)
7073for (const [i, r] of rows.entries()) {
7174 if (!r.name || !r.type) throw new Error(`row ${i} missing name/type`)
72 if (!r.driveKey && !r.link) throw new Error(`row ${i} (${r.name}) has neither driveKey nor link`)
75 if (!r.driveKey && !r.link && !r.legacyMigrationId) throw new Error(`row ${i} (${r.name}) has neither driveKey, link, nor legacy migration ID`)
7376}
7477
7578const header = `/**
test/release-packaging.test.js +5 -3
……@@ -270,7 +270,7 @@ test('Pear stage ignore excludes local release/operator scratch files', () => {
270270 assert.ok(ignored.includes('/test'))
271271})
272272
273test('PearBrowser catalogue release row stays in sync with package and public addresses', () => {
273test('PearBrowser catalogue row is a migration-required native record, not a remote launcher', () => {
274274 const source = catalogSource.apps.find((app) => app.id === 'pearbrowser-desktop')
275275 const seed = SEED_APPS.find((app) => app.name === 'PearBrowser Desktop')
276276 const homepageKey = '03f0060a35451cfb6b68ad1dda1b8474ebb43fd9100071ccf7d67679a83ebb4f'
……@@ -278,11 +278,13 @@ test('PearBrowser catalogue release row stays in sync with package and public ad
278278 assert.ok(source, 'catalogue source row missing')
279279 assert.ok(seed, 'offline catalogue seed row missing')
280280 assert.equal(source.version, pkg.version)
281 assert.equal(source.pearLink, pearConfig.links.production)
282 assert.equal(source.link, pearConfig.links.production)
281 assert.equal(source.legacyMigrationId, pearConfig.links.production.replace(/^pear:\/\//, ''))
282 assert.equal(source.nativeDelivery?.status, 'migration-required')
283283 assert.equal(source.driveKey, homepageKey)
284284 assert.equal(source.homepage, `hyper://${homepageKey}/`)
285 assert.equal(source.link, source.homepage)
285286 assert.equal(seed.version, source.version)
287 assert.equal(seed.nativeDeliveryStatus, source.nativeDelivery.status)
286288 assert.equal(seed.link, source.link)
287289 assert.equal(seed.driveKey, source.driveKey)
288290 assert.equal(seed.homepage, source.homepage)