diff --git a/.gitignore b/.gitignore index 6cfd42f0..f0e76d22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ node_modules -# Output +# Output (match anywhere — monorepo) .output .vercel .netlify .wrangler -/.svelte-kit -/build +.svelte-kit +build +.turbo +/diff-snapshots*/ +backups/ # OS .DS_Store @@ -28,6 +31,6 @@ sveltekit-cloudflare-workers inlay -scripts/backups +**/scripts/backups theming/ \ No newline at end of file diff --git a/eslint.config.js b/apps/web/eslint.config.js similarity index 100% rename from eslint.config.js rename to apps/web/eslint.config.js diff --git a/apps/web/lex.config.js b/apps/web/lex.config.js new file mode 100644 index 00000000..85eafdbf --- /dev/null +++ b/apps/web/lex.config.js @@ -0,0 +1,25 @@ +import { defineLexiconConfig } from "@atcute/lex-cli"; + +export default defineLexiconConfig({ + files: ["lexicons/custom/**/*.json", "lexicons/pulled/**/*.json", "lexicons/generated/**/*.json"], + outdir: "src/lexicon-types/", + imports: ["@atcute/atproto"], + pull: { + outdir: "lexicons/pulled/", + sources: [ + { + type: "atproto", + mode: "nsids", + nsids: [ + "app.blento.card", + "app.blento.node", + "app.blento.page", + "app.blento.section", + "app.bsky.actor.profile", + "app.nearhorizon.actor.pronouns", + "site.standard.publication" + ], + }, + ], + }, +}); diff --git a/lexicons/custom/app/blento/card.json b/apps/web/lexicons/custom/app/blento/card.json similarity index 100% rename from lexicons/custom/app/blento/card.json rename to apps/web/lexicons/custom/app/blento/card.json diff --git a/apps/web/lexicons/custom/app/blento/defs.json b/apps/web/lexicons/custom/app/blento/defs.json new file mode 100644 index 00000000..fcd6d78c --- /dev/null +++ b/apps/web/lexicons/custom/app/blento/defs.json @@ -0,0 +1,40 @@ +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "app.blento.defs", + "description": "Shared content/layout shapes referenced by app.blento.node's open blobs. Typed cards (#link, #image, …) are added additively; #card / #container are the generic fallbacks.", + "defs": { + "card": { + "type": "object", + "description": "Generic leaf content fallback. cardType discriminates; remaining fields are the card's opaque data.", + "required": ["cardType"], + "properties": { + "cardType": { "type": "string" } + } + }, + "container": { + "type": "object", + "description": "Generic container content fallback. containerType discriminates; remaining fields are the container config.", + "required": ["containerType"], + "properties": { + "containerType": { "type": "string" } + } + }, + "gridCell": { + "type": "object", + "description": "Position within a grid container. Integers only.", + "required": ["x", "y", "w", "h"], + "properties": { + "x": { "type": "integer" }, + "y": { "type": "integer" }, + "w": { "type": "integer" }, + "h": { "type": "integer" }, + "mobileX": { "type": "integer" }, + "mobileY": { "type": "integer" }, + "mobileW": { "type": "integer" }, + "mobileH": { "type": "integer" }, + "rotation": { "type": "integer" } + } + } + } +} diff --git a/apps/web/lexicons/custom/app/blento/node.json b/apps/web/lexicons/custom/app/blento/node.json new file mode 100644 index 00000000..b8f8982d --- /dev/null +++ b/apps/web/lexicons/custom/app/blento/node.json @@ -0,0 +1,27 @@ +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "app.blento.node", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["kind", "rank", "page", "content", "version"], + "properties": { + "kind": { "type": "string", "knownValues": ["document", "container", "leaf"] }, + "parent": { "type": "string" }, + "rank": { "type": "string" }, + "page": { "type": "string" }, + "content": { "type": "unknown" }, + "layout": { "type": "unknown" }, + "style": { "type": "unknown" }, + "source": { "type": "unknown" }, + "updatedAt": { "type": "string", "format": "datetime" }, + "version": { "type": "integer" } + } + } + } + } +} diff --git a/lexicons/custom/app/blento/page.json b/apps/web/lexicons/custom/app/blento/page.json similarity index 100% rename from lexicons/custom/app/blento/page.json rename to apps/web/lexicons/custom/app/blento/page.json diff --git a/lexicons/custom/app/blento/section.json b/apps/web/lexicons/custom/app/blento/section.json similarity index 100% rename from lexicons/custom/app/blento/section.json rename to apps/web/lexicons/custom/app/blento/section.json diff --git a/apps/web/lexicons/generated/app/blento/authFull.json b/apps/web/lexicons/generated/app/blento/authFull.json new file mode 100644 index 00000000..1bc0fde5 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/authFull.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.blento.authFull", + "defs": { + "main": { + "type": "permission-set", + "title": "app.blento", + "description": "Full access to the app.blento service", + "permissions": [ + { + "type": "permission", + "resource": "rpc", + "aud": "*", + "lxm": [ + "app.blento.card.getRecord", + "app.blento.card.listRecords", + "app.blento.getCursor", + "app.blento.getOverview", + "app.blento.getProfile", + "app.blento.node.getRecord", + "app.blento.node.listRecords", + "app.blento.notifyOfUpdate", + "app.blento.page.getRecord", + "app.blento.page.listRecords", + "app.blento.section.getRecord", + "app.blento.section.listRecords" + ] + }, + { + "type": "permission", + "resource": "repo", + "collection": [ + "app.blento.card", + "app.blento.node", + "app.blento.page", + "app.blento.section" + ] + } + ] + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/card/getRecord.json b/apps/web/lexicons/generated/app/blento/card/getRecord.json new file mode 100644 index 00000000..b109971e --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/card/getRecord.json @@ -0,0 +1,109 @@ +{ + "lexicon": 1, + "id": "app.blento.card.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.card record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/card/listRecords.json b/apps/web/lexicons/generated/app/blento/card/listRecords.json new file mode 100644 index 00000000..4dc7e658 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/card/listRecords.json @@ -0,0 +1,158 @@ +{ + "lexicon": 1, + "id": "app.blento.card.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.card records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "page": { + "type": "string", + "description": "Filter by page" + }, + "cardType": { + "type": "string", + "description": "Filter by cardType" + }, + "sort": { + "type": "string", + "knownValues": [ + "page", + "cardType" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/getCursor.json b/apps/web/lexicons/generated/app/blento/getCursor.json new file mode 100644 index 00000000..555c1260 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/getCursor.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "app.blento.getCursor", + "defs": { + "main": { + "type": "query", + "description": "Get the current cursor position", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "time_us": { + "type": "integer" + }, + "date": { + "type": "string" + }, + "seconds_ago": { + "type": "integer" + } + } + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/getOverview.json b/apps/web/lexicons/generated/app/blento/getOverview.json new file mode 100644 index 00000000..02050d61 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/getOverview.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "app.blento.getOverview", + "defs": { + "main": { + "type": "query", + "description": "Get an overview of all indexed collections", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "total_records", + "collections" + ], + "properties": { + "total_records": { + "type": "integer" + }, + "collections": { + "type": "array", + "items": { + "type": "ref", + "ref": "#collectionStats" + } + } + } + } + } + }, + "collectionStats": { + "type": "object", + "required": [ + "collection", + "records", + "unique_users" + ], + "properties": { + "collection": { + "type": "string" + }, + "records": { + "type": "integer" + }, + "unique_users": { + "type": "integer" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/getProfile.json b/apps/web/lexicons/generated/app/blento/getProfile.json new file mode 100644 index 00000000..8a21f9b3 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/getProfile.json @@ -0,0 +1,74 @@ +{ + "lexicon": 1, + "id": "app.blento.getProfile", + "defs": { + "main": { + "type": "query", + "description": "Get a user's profiles by DID or handle", + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "DID or handle of the user" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "profiles" + ], + "properties": { + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/node/getRecord.json b/apps/web/lexicons/generated/app/blento/node/getRecord.json new file mode 100644 index 00000000..a1189744 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/node/getRecord.json @@ -0,0 +1,109 @@ +{ + "lexicon": 1, + "id": "app.blento.node.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.node record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.node#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/node/listRecords.json b/apps/web/lexicons/generated/app/blento/node/listRecords.json new file mode 100644 index 00000000..a4303bc6 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/node/listRecords.json @@ -0,0 +1,158 @@ +{ + "lexicon": 1, + "id": "app.blento.node.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.node records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "page": { + "type": "string", + "description": "Filter by page" + }, + "type": { + "type": "string", + "description": "Filter by type" + }, + "sort": { + "type": "string", + "knownValues": [ + "page", + "type" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.node#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/notifyOfUpdate.json b/apps/web/lexicons/generated/app/blento/notifyOfUpdate.json new file mode 100644 index 00000000..08382b7f --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/notifyOfUpdate.json @@ -0,0 +1,59 @@ +{ + "lexicon": 1, + "id": "app.blento.notifyOfUpdate", + "defs": { + "main": { + "type": "procedure", + "description": "Notify of a record change for immediate indexing. Fetches the record from the user's PDS and indexes (or deletes) it.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "Single AT URI to fetch and index" + }, + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25, + "description": "Batch of AT URIs to fetch and index (max 25)" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "indexed", + "deleted" + ], + "properties": { + "indexed": { + "type": "integer", + "description": "Number of records created or updated" + }, + "deleted": { + "type": "integer", + "description": "Number of records deleted (not found on PDS)" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Errors for individual URIs that could not be processed" + } + } + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/page/getRecord.json b/apps/web/lexicons/generated/app/blento/page/getRecord.json new file mode 100644 index 00000000..41b74a92 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/page/getRecord.json @@ -0,0 +1,109 @@ +{ + "lexicon": 1, + "id": "app.blento.page.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.page record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/page/listRecords.json b/apps/web/lexicons/generated/app/blento/page/listRecords.json new file mode 100644 index 00000000..79b6886a --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/page/listRecords.json @@ -0,0 +1,134 @@ +{ + "lexicon": 1, + "id": "app.blento.page.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.page records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/section/getRecord.json b/apps/web/lexicons/generated/app/blento/section/getRecord.json new file mode 100644 index 00000000..9fbf1b57 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/section/getRecord.json @@ -0,0 +1,109 @@ +{ + "lexicon": 1, + "id": "app.blento.section.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.section record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.section#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/app/blento/section/listRecords.json b/apps/web/lexicons/generated/app/blento/section/listRecords.json new file mode 100644 index 00000000..81b12f38 --- /dev/null +++ b/apps/web/lexicons/generated/app/blento/section/listRecords.json @@ -0,0 +1,158 @@ +{ + "lexicon": 1, + "id": "app.blento.section.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.section records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "page": { + "type": "string", + "description": "Filter by page" + }, + "sectionType": { + "type": "string", + "description": "Filter by sectionType" + }, + "sort": { + "type": "string", + "knownValues": [ + "page", + "sectionType" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "app.blento.section#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/apps/web/lexicons/generated/index.ts b/apps/web/lexicons/generated/index.ts new file mode 100644 index 00000000..14945760 --- /dev/null +++ b/apps/web/lexicons/generated/index.ts @@ -0,0 +1,24 @@ +// Auto-generated by @atmo-dev/contrail-lexicons. Do not edit. +// Pass `lexicons` to `createWorker(config, { lexicons })` to expose them +// at `/xrpc/.lexicons` for consumer apps to typegen against. + +import _0 from "../custom/app/blento/card.json"; +import _1 from "../custom/app/blento/defs.json"; +import _2 from "../custom/app/blento/node.json"; +import _3 from "../custom/app/blento/page.json"; +import _4 from "../custom/app/blento/section.json"; +import _5 from "./app/blento/authFull.json"; +import _6 from "./app/blento/card/getRecord.json"; +import _7 from "./app/blento/card/listRecords.json"; +import _8 from "./app/blento/getCursor.json"; +import _9 from "./app/blento/getOverview.json"; +import _10 from "./app/blento/getProfile.json"; +import _11 from "./app/blento/node/getRecord.json"; +import _12 from "./app/blento/node/listRecords.json"; +import _13 from "./app/blento/notifyOfUpdate.json"; +import _14 from "./app/blento/page/getRecord.json"; +import _15 from "./app/blento/page/listRecords.json"; +import _16 from "./app/blento/section/getRecord.json"; +import _17 from "./app/blento/section/listRecords.json"; + +export const lexicons: object[] = [_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17]; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000..535ab943 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,130 @@ +{ + "name": "@blento/web", + "private": true, + "version": "0.2.0", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build && contrail append-scheduled", + "preview": "pnpm run build && wrangler dev", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "eslint --fix . && prettier --write .", + "test": "vitest run", + "deploy": "pnpm run build && wrangler deploy", + "cf-typegen": "wrangler types ./src/worker-configuration.d.ts", + "env:generate-key": "npx tsx src/lib/atproto/scripts/generate-key.ts", + "env:generate-secret": "npx tsx src/lib/atproto/scripts/generate-secret.ts", + "env:setup-dev": "npx tsx src/lib/atproto/scripts/setup-dev.ts", + "tunnel": "npx tsx src/lib/atproto/scripts/tunnel.ts", + "generate": "contrail-lex generate", + "backfill": "contrail backfill", + "backfill:remote": "contrail backfill --remote" + }, + "devDependencies": { + "@atcute/lex-cli": "^2.8.1", + "@atcute/lexicon-doc": "^2.1.2", + "@atmo-dev/contrail-lexicons": "^0.4.5", + "@eslint/compat": "^2.0.3", + "@eslint/js": "^10.0.1", + "@sveltejs/adapter-cloudflare": "^7.2.8", + "@sveltejs/kit": "^2.55.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tailwindcss/forms": "^0.5.11", + "@tailwindcss/vite": "^4.2.1", + "@types/node": "^25.9.1", + "@types/turndown": "^5.0.6", + "eslint": "^10.0.3", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-svelte": "^3.15.2", + "globals": "^17.4.0", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.1", + "prettier-plugin-tailwindcss": "^0.7.2", + "svelte": "^5.53.11", + "svelte-check": "^4.4.5", + "svelte-maplibre-gl": "^1.0.3", + "tailwindcss": "^4.2.1", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.0", + "valibot": "^1.3.1", + "vite": "^8.0.0", + "vitest": "^4.1.4" + }, + "dependencies": { + "@atcute/atproto": "^3.1.10", + "@atcute/bluesky": "^3.3.0", + "@atcute/bluesky-richtext-parser": "^2.1.1", + "@atcute/bluesky-richtext-segmenter": "^3.0.0", + "@atcute/client": "^4.2.1", + "@atcute/identity-resolver": "^1.2.2", + "@atcute/lexicons": "^1.3.0", + "@atcute/oauth-browser-client": "^3.0.0", + "@atcute/oauth-node-client": "^1.1.0", + "@atcute/standard-site": "^1.0.1", + "@atcute/tid": "^1.1.2", + "@atmo-dev/contrail": "^0.5.0", + "@atmo-dev/events-ui": "^0.1.0", + "@blento/schema": "workspace:*", + "@cloudflare/workers-types": "^4.20260313.1", + "@ethercorps/sveltekit-og": "^4.2.1", + "@floating-ui/dom": "^1.7.6", + "@foxui/3d": "^0.8.5", + "@foxui/colors": "^0.8.5", + "@foxui/core": "^0.9.1", + "@foxui/social": "^0.8.9", + "@foxui/text": "^0.8.5", + "@foxui/time": "^0.8.5", + "@foxui/visual": "^0.8.7", + "@internationalized/date": "^3.12.0", + "@number-flow/svelte": "^0.4.0", + "@tailwindcss/typography": "^0.5.19", + "@threlte/core": "^8.4.1", + "@threlte/extras": "^9.8.1", + "@tiptap/core": "^3.20.1", + "@tiptap/extension-bubble-menu": "^3.20.1", + "@tiptap/extension-code-block-lowlight": "^3.20.1", + "@tiptap/extension-document": "^3.20.1", + "@tiptap/extension-image": "^3.20.1", + "@tiptap/extension-link": "^3.20.1", + "@tiptap/extension-paragraph": "^3.20.1", + "@tiptap/extension-placeholder": "^3.20.1", + "@tiptap/extension-text": "^3.20.1", + "@tiptap/extension-typography": "^3.20.1", + "@tiptap/extension-underline": "^3.20.1", + "@tiptap/markdown": "^3.20.1", + "@tiptap/pm": "^3.20.1", + "@tiptap/starter-kit": "^3.20.1", + "@tiptap/suggestion": "^3.20.1", + "@types/three": "^0.183.1", + "bits-ui": "^2.16.3", + "clsx": "^2.1.1", + "dompurify": "^3.3.3", + "fast-xml-parser": "^5.9.3", + "gsap": "^3.14.2", + "hls.js": "^1.6.15", + "leaflet": "^1.9.4", + "link-preview-js": "^4.0.0", + "lowlight": "^3.3.0", + "maplibre-gl": "^5.20.0", + "marked": "^17.0.4", + "perfect-freehand": "^1.2.3", + "plyr": "^3.8.4", + "qr-code-styling": "^1.9.2", + "react-grid-layout": "^2.2.2", + "simple-icons": "^16.11.0", + "svelte-boring-avatars": "^1.2.6", + "svelte-sonner": "^1.1.0", + "svelte-tiptap": "^3.0.1", + "tailwind-merge": "^3.5.0", + "tailwind-variants": "^3.2.2", + "tailwindcss-animate": "^1.0.7", + "three": "^0.183.2", + "turndown": "^7.2.2", + "wrangler": "^4.73.0" + }, + "license": "MIT" +} diff --git a/scripts/atproto.ts b/apps/web/scripts/atproto.ts similarity index 100% rename from scripts/atproto.ts rename to apps/web/scripts/atproto.ts diff --git a/scripts/simulate-load.ts b/apps/web/scripts/simulate-load.ts similarity index 100% rename from scripts/simulate-load.ts rename to apps/web/scripts/simulate-load.ts diff --git a/src/app.css b/apps/web/src/app.css similarity index 100% rename from src/app.css rename to apps/web/src/app.css diff --git a/src/app.d.ts b/apps/web/src/app.d.ts similarity index 100% rename from src/app.d.ts rename to apps/web/src/app.d.ts diff --git a/src/app.html b/apps/web/src/app.html similarity index 100% rename from src/app.html rename to apps/web/src/app.html diff --git a/src/hooks.server.ts b/apps/web/src/hooks.server.ts similarity index 100% rename from src/hooks.server.ts rename to apps/web/src/hooks.server.ts diff --git a/src/lexicon-types/index.ts b/apps/web/src/lexicon-types/index.ts similarity index 77% rename from src/lexicon-types/index.ts rename to apps/web/src/lexicon-types/index.ts index fb11cde2..6824b8f1 100644 --- a/src/lexicon-types/index.ts +++ b/apps/web/src/lexicon-types/index.ts @@ -1,9 +1,13 @@ export * as AppBlentoCard from './types/app/blento/card.js'; export * as AppBlentoCardGetRecord from './types/app/blento/card/getRecord.js'; export * as AppBlentoCardListRecords from './types/app/blento/card/listRecords.js'; +export * as AppBlentoDefs from './types/app/blento/defs.js'; export * as AppBlentoGetCursor from './types/app/blento/getCursor.js'; export * as AppBlentoGetOverview from './types/app/blento/getOverview.js'; export * as AppBlentoGetProfile from './types/app/blento/getProfile.js'; +export * as AppBlentoNode from './types/app/blento/node.js'; +export * as AppBlentoNodeGetRecord from './types/app/blento/node/getRecord.js'; +export * as AppBlentoNodeListRecords from './types/app/blento/node/listRecords.js'; export * as AppBlentoNotifyOfUpdate from './types/app/blento/notifyOfUpdate.js'; export * as AppBlentoPage from './types/app/blento/page.js'; export * as AppBlentoPageGetRecord from './types/app/blento/page/getRecord.js'; diff --git a/src/lexicon-types/types/app/blento/card.ts b/apps/web/src/lexicon-types/types/app/blento/card.ts similarity index 100% rename from src/lexicon-types/types/app/blento/card.ts rename to apps/web/src/lexicon-types/types/app/blento/card.ts diff --git a/src/lexicon-types/types/app/blento/card/getRecord.ts b/apps/web/src/lexicon-types/types/app/blento/card/getRecord.ts similarity index 100% rename from src/lexicon-types/types/app/blento/card/getRecord.ts rename to apps/web/src/lexicon-types/types/app/blento/card/getRecord.ts diff --git a/src/lexicon-types/types/app/blento/card/listRecords.ts b/apps/web/src/lexicon-types/types/app/blento/card/listRecords.ts similarity index 100% rename from src/lexicon-types/types/app/blento/card/listRecords.ts rename to apps/web/src/lexicon-types/types/app/blento/card/listRecords.ts diff --git a/apps/web/src/lexicon-types/types/app/blento/defs.ts b/apps/web/src/lexicon-types/types/app/blento/defs.ts new file mode 100644 index 00000000..99920cf2 --- /dev/null +++ b/apps/web/src/lexicon-types/types/app/blento/defs.ts @@ -0,0 +1,39 @@ +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; + +const _cardSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.defs#card')), + cardType: /*#__PURE__*/ v.string() +}); +const _containerSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.defs#container')), + containerType: /*#__PURE__*/ v.string() +}); +const _gridCellSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.defs#gridCell')), + h: /*#__PURE__*/ v.integer(), + mobileH: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + mobileW: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + mobileX: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + mobileY: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + rotation: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + w: /*#__PURE__*/ v.integer(), + x: /*#__PURE__*/ v.integer(), + y: /*#__PURE__*/ v.integer() +}); + +type card$schematype = typeof _cardSchema; +type container$schematype = typeof _containerSchema; +type gridCell$schematype = typeof _gridCellSchema; + +export interface cardSchema extends card$schematype {} +export interface containerSchema extends container$schematype {} +export interface gridCellSchema extends gridCell$schematype {} + +export const cardSchema = _cardSchema as cardSchema; +export const containerSchema = _containerSchema as containerSchema; +export const gridCellSchema = _gridCellSchema as gridCellSchema; + +export interface Card extends v.InferInput {} +export interface Container extends v.InferInput {} +export interface GridCell extends v.InferInput {} diff --git a/src/lexicon-types/types/app/blento/getCursor.ts b/apps/web/src/lexicon-types/types/app/blento/getCursor.ts similarity index 100% rename from src/lexicon-types/types/app/blento/getCursor.ts rename to apps/web/src/lexicon-types/types/app/blento/getCursor.ts diff --git a/src/lexicon-types/types/app/blento/getOverview.ts b/apps/web/src/lexicon-types/types/app/blento/getOverview.ts similarity index 100% rename from src/lexicon-types/types/app/blento/getOverview.ts rename to apps/web/src/lexicon-types/types/app/blento/getOverview.ts diff --git a/src/lexicon-types/types/app/blento/getProfile.ts b/apps/web/src/lexicon-types/types/app/blento/getProfile.ts similarity index 100% rename from src/lexicon-types/types/app/blento/getProfile.ts rename to apps/web/src/lexicon-types/types/app/blento/getProfile.ts diff --git a/apps/web/src/lexicon-types/types/app/blento/node.ts b/apps/web/src/lexicon-types/types/app/blento/node.ts new file mode 100644 index 00000000..3a8309a4 --- /dev/null +++ b/apps/web/src/lexicon-types/types/app/blento/node.ts @@ -0,0 +1,34 @@ +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; + +const _mainSchema = /*#__PURE__*/ v.record( + /*#__PURE__*/ v.tidString(), + /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.literal('app.blento.node'), + content: /*#__PURE__*/ v.unknown(), + kind: /*#__PURE__*/ v.string<'container' | 'document' | 'leaf' | (string & {})>(), + layout: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + page: /*#__PURE__*/ v.string(), + parent: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + rank: /*#__PURE__*/ v.string(), + source: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + style: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), + version: /*#__PURE__*/ v.integer() + }) +); + +type main$schematype = typeof _mainSchema; + +export interface mainSchema extends main$schematype {} + +export const mainSchema = _mainSchema as mainSchema; + +export interface Main extends v.InferInput {} + +declare module '@atcute/lexicons/ambient' { + interface Records { + 'app.blento.node': mainSchema; + } +} diff --git a/apps/web/src/lexicon-types/types/app/blento/node/getRecord.ts b/apps/web/src/lexicon-types/types/app/blento/node/getRecord.ts new file mode 100644 index 00000000..6b3d7023 --- /dev/null +++ b/apps/web/src/lexicon-types/types/app/blento/node/getRecord.ts @@ -0,0 +1,66 @@ +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoNode from '../node.js'; + +const _mainSchema = /*#__PURE__*/ v.query('app.blento.node.getRecord', { + params: /*#__PURE__*/ v.object({ + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * AT URI of the record + */ + uri: /*#__PURE__*/ v.resourceUriString() + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), + get value() { + return AppBlentoNode.mainSchema; + } + }) + } +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.node.getRecord#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + value: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()) +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; + +export interface ProfileEntry extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.node.getRecord': mainSchema; + } +} diff --git a/apps/web/src/lexicon-types/types/app/blento/node/listRecords.ts b/apps/web/src/lexicon-types/types/app/blento/node/listRecords.ts new file mode 100644 index 00000000..dfabbfae --- /dev/null +++ b/apps/web/src/lexicon-types/types/app/blento/node/listRecords.ts @@ -0,0 +1,103 @@ +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoNode from '../node.js'; + +const _mainSchema = /*#__PURE__*/ v.query('app.blento.node.listRecords', { + params: /*#__PURE__*/ v.object({ + /** + * Filter by DID or handle (triggers on-demand backfill) + */ + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @minimum 1 + * @maximum 200 + * @default 50 + */ + limit: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [/*#__PURE__*/ v.integerRange(1, 200)]), + 50 + ), + /** + * Sort direction (default: desc for dates/numbers/counts, asc for strings) + */ + order: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string<'asc' | 'desc' | (string & {})>()), + /** + * Filter by page + */ + page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * Field to sort by (default: time_us) + */ + sort: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string<'page' | 'type' | (string & {})>()), + /** + * Filter by type + */ + type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()) + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + get records() { + return /*#__PURE__*/ v.array(recordSchema); + } + }) + } +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.node.listRecords#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.cidString()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + value: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()) +}); +const _recordSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.node.listRecords#record')), + cid: /*#__PURE__*/ v.cidString(), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), + get value() { + return AppBlentoNode.mainSchema; + } +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; +type record$schematype = typeof _recordSchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} +export interface recordSchema extends record$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; +export const recordSchema = _recordSchema as recordSchema; + +export interface ProfileEntry extends v.InferInput {} +export interface Record extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.node.listRecords': mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/notifyOfUpdate.ts b/apps/web/src/lexicon-types/types/app/blento/notifyOfUpdate.ts similarity index 100% rename from src/lexicon-types/types/app/blento/notifyOfUpdate.ts rename to apps/web/src/lexicon-types/types/app/blento/notifyOfUpdate.ts diff --git a/src/lexicon-types/types/app/blento/page.ts b/apps/web/src/lexicon-types/types/app/blento/page.ts similarity index 100% rename from src/lexicon-types/types/app/blento/page.ts rename to apps/web/src/lexicon-types/types/app/blento/page.ts diff --git a/src/lexicon-types/types/app/blento/page/getRecord.ts b/apps/web/src/lexicon-types/types/app/blento/page/getRecord.ts similarity index 100% rename from src/lexicon-types/types/app/blento/page/getRecord.ts rename to apps/web/src/lexicon-types/types/app/blento/page/getRecord.ts diff --git a/src/lexicon-types/types/app/blento/page/listRecords.ts b/apps/web/src/lexicon-types/types/app/blento/page/listRecords.ts similarity index 100% rename from src/lexicon-types/types/app/blento/page/listRecords.ts rename to apps/web/src/lexicon-types/types/app/blento/page/listRecords.ts diff --git a/src/lexicon-types/types/app/blento/section.ts b/apps/web/src/lexicon-types/types/app/blento/section.ts similarity index 100% rename from src/lexicon-types/types/app/blento/section.ts rename to apps/web/src/lexicon-types/types/app/blento/section.ts diff --git a/src/lexicon-types/types/app/blento/section/getRecord.ts b/apps/web/src/lexicon-types/types/app/blento/section/getRecord.ts similarity index 100% rename from src/lexicon-types/types/app/blento/section/getRecord.ts rename to apps/web/src/lexicon-types/types/app/blento/section/getRecord.ts diff --git a/src/lexicon-types/types/app/blento/section/listRecords.ts b/apps/web/src/lexicon-types/types/app/blento/section/listRecords.ts similarity index 100% rename from src/lexicon-types/types/app/blento/section/listRecords.ts rename to apps/web/src/lexicon-types/types/app/blento/section/listRecords.ts diff --git a/src/lib/atproto/LoginModal.svelte b/apps/web/src/lib/atproto/LoginModal.svelte similarity index 100% rename from src/lib/atproto/LoginModal.svelte rename to apps/web/src/lib/atproto/LoginModal.svelte diff --git a/src/lib/atproto/auth.svelte.ts b/apps/web/src/lib/atproto/auth.svelte.ts similarity index 100% rename from src/lib/atproto/auth.svelte.ts rename to apps/web/src/lib/atproto/auth.svelte.ts diff --git a/src/lib/atproto/image-helper.ts b/apps/web/src/lib/atproto/image-helper.ts similarity index 100% rename from src/lib/atproto/image-helper.ts rename to apps/web/src/lib/atproto/image-helper.ts diff --git a/src/lib/atproto/index.ts b/apps/web/src/lib/atproto/index.ts similarity index 100% rename from src/lib/atproto/index.ts rename to apps/web/src/lib/atproto/index.ts diff --git a/src/lib/atproto/methods.ts b/apps/web/src/lib/atproto/methods.ts similarity index 100% rename from src/lib/atproto/methods.ts rename to apps/web/src/lib/atproto/methods.ts diff --git a/src/lib/atproto/scripts/generate-key.ts b/apps/web/src/lib/atproto/scripts/generate-key.ts similarity index 100% rename from src/lib/atproto/scripts/generate-key.ts rename to apps/web/src/lib/atproto/scripts/generate-key.ts diff --git a/src/lib/atproto/scripts/generate-secret.ts b/apps/web/src/lib/atproto/scripts/generate-secret.ts similarity index 100% rename from src/lib/atproto/scripts/generate-secret.ts rename to apps/web/src/lib/atproto/scripts/generate-secret.ts diff --git a/src/lib/atproto/scripts/setup-dev.ts b/apps/web/src/lib/atproto/scripts/setup-dev.ts similarity index 100% rename from src/lib/atproto/scripts/setup-dev.ts rename to apps/web/src/lib/atproto/scripts/setup-dev.ts diff --git a/src/lib/atproto/scripts/tunnel.ts b/apps/web/src/lib/atproto/scripts/tunnel.ts similarity index 100% rename from src/lib/atproto/scripts/tunnel.ts rename to apps/web/src/lib/atproto/scripts/tunnel.ts diff --git a/src/lib/atproto/server/kv-store.ts b/apps/web/src/lib/atproto/server/kv-store.ts similarity index 100% rename from src/lib/atproto/server/kv-store.ts rename to apps/web/src/lib/atproto/server/kv-store.ts diff --git a/src/lib/atproto/server/oauth.remote.ts b/apps/web/src/lib/atproto/server/oauth.remote.ts similarity index 100% rename from src/lib/atproto/server/oauth.remote.ts rename to apps/web/src/lib/atproto/server/oauth.remote.ts diff --git a/src/lib/atproto/server/oauth.ts b/apps/web/src/lib/atproto/server/oauth.ts similarity index 100% rename from src/lib/atproto/server/oauth.ts rename to apps/web/src/lib/atproto/server/oauth.ts diff --git a/src/lib/atproto/server/repo.remote.ts b/apps/web/src/lib/atproto/server/repo.remote.ts similarity index 100% rename from src/lib/atproto/server/repo.remote.ts rename to apps/web/src/lib/atproto/server/repo.remote.ts diff --git a/src/lib/atproto/server/scopes.ts b/apps/web/src/lib/atproto/server/scopes.ts similarity index 100% rename from src/lib/atproto/server/scopes.ts rename to apps/web/src/lib/atproto/server/scopes.ts diff --git a/src/lib/atproto/server/session.ts b/apps/web/src/lib/atproto/server/session.ts similarity index 100% rename from src/lib/atproto/server/session.ts rename to apps/web/src/lib/atproto/server/session.ts diff --git a/src/lib/atproto/server/signed-cookie.ts b/apps/web/src/lib/atproto/server/signed-cookie.ts similarity index 100% rename from src/lib/atproto/server/signed-cookie.ts rename to apps/web/src/lib/atproto/server/signed-cookie.ts diff --git a/src/lib/atproto/server/video.remote.ts b/apps/web/src/lib/atproto/server/video.remote.ts similarity index 100% rename from src/lib/atproto/server/video.remote.ts rename to apps/web/src/lib/atproto/server/video.remote.ts diff --git a/src/lib/atproto/settings.ts b/apps/web/src/lib/atproto/settings.ts similarity index 98% rename from src/lib/atproto/settings.ts rename to apps/web/src/lib/atproto/settings.ts index eef6f7be..4d8f035a 100644 --- a/src/lib/atproto/settings.ts +++ b/apps/web/src/lib/atproto/settings.ts @@ -7,6 +7,7 @@ export const collections = [ 'app.blento.card', 'app.blento.page', 'app.blento.section', + 'app.blento.node', 'app.blento.settings', 'app.blento.comment', 'app.blento.guestbook.entry', diff --git a/src/lib/cards/_base/BaseCard/BaseCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte similarity index 100% rename from src/lib/cards/_base/BaseCard/BaseCard.svelte rename to apps/web/src/lib/cards/_base/BaseCard/BaseCard.svelte diff --git a/src/lib/cards/_base/BaseCard/BaseEditingCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/BaseEditingCard.svelte similarity index 100% rename from src/lib/cards/_base/BaseCard/BaseEditingCard.svelte rename to apps/web/src/lib/cards/_base/BaseCard/BaseEditingCard.svelte diff --git a/src/lib/cards/_base/BaseCard/GridBaseCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/GridBaseCard.svelte similarity index 100% rename from src/lib/cards/_base/BaseCard/GridBaseCard.svelte rename to apps/web/src/lib/cards/_base/BaseCard/GridBaseCard.svelte diff --git a/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte b/apps/web/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte similarity index 100% rename from src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte rename to apps/web/src/lib/cards/_base/BaseCard/GridBaseEditingCard.svelte diff --git a/src/lib/cards/_base/Card/Card.svelte b/apps/web/src/lib/cards/_base/Card/Card.svelte similarity index 100% rename from src/lib/cards/_base/Card/Card.svelte rename to apps/web/src/lib/cards/_base/Card/Card.svelte diff --git a/src/lib/cards/_base/Card/EditingCard.svelte b/apps/web/src/lib/cards/_base/Card/EditingCard.svelte similarity index 100% rename from src/lib/cards/_base/Card/EditingCard.svelte rename to apps/web/src/lib/cards/_base/Card/EditingCard.svelte diff --git a/src/lib/cards/_settings/SettingsAlignToggle.svelte b/apps/web/src/lib/cards/_settings/SettingsAlignToggle.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsAlignToggle.svelte rename to apps/web/src/lib/cards/_settings/SettingsAlignToggle.svelte diff --git a/src/lib/cards/_settings/SettingsField.svelte b/apps/web/src/lib/cards/_settings/SettingsField.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsField.svelte rename to apps/web/src/lib/cards/_settings/SettingsField.svelte diff --git a/src/lib/cards/_settings/SettingsImagePicker.svelte b/apps/web/src/lib/cards/_settings/SettingsImagePicker.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsImagePicker.svelte rename to apps/web/src/lib/cards/_settings/SettingsImagePicker.svelte diff --git a/src/lib/cards/_settings/SettingsLinkField.svelte b/apps/web/src/lib/cards/_settings/SettingsLinkField.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsLinkField.svelte rename to apps/web/src/lib/cards/_settings/SettingsLinkField.svelte diff --git a/src/lib/cards/_settings/SettingsSection.svelte b/apps/web/src/lib/cards/_settings/SettingsSection.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsSection.svelte rename to apps/web/src/lib/cards/_settings/SettingsSection.svelte diff --git a/src/lib/cards/_settings/SettingsSegmented.svelte b/apps/web/src/lib/cards/_settings/SettingsSegmented.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsSegmented.svelte rename to apps/web/src/lib/cards/_settings/SettingsSegmented.svelte diff --git a/src/lib/cards/_settings/SettingsTextSize.svelte b/apps/web/src/lib/cards/_settings/SettingsTextSize.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsTextSize.svelte rename to apps/web/src/lib/cards/_settings/SettingsTextSize.svelte diff --git a/src/lib/cards/_settings/SettingsToggle.svelte b/apps/web/src/lib/cards/_settings/SettingsToggle.svelte similarity index 100% rename from src/lib/cards/_settings/SettingsToggle.svelte rename to apps/web/src/lib/cards/_settings/SettingsToggle.svelte diff --git a/src/lib/cards/_settings/SourceSettings.svelte b/apps/web/src/lib/cards/_settings/SourceSettings.svelte similarity index 100% rename from src/lib/cards/_settings/SourceSettings.svelte rename to apps/web/src/lib/cards/_settings/SourceSettings.svelte diff --git a/src/lib/cards/_settings/index.ts b/apps/web/src/lib/cards/_settings/index.ts similarity index 100% rename from src/lib/cards/_settings/index.ts rename to apps/web/src/lib/cards/_settings/index.ts diff --git a/src/lib/cards/colors.ts b/apps/web/src/lib/cards/colors.ts similarity index 100% rename from src/lib/cards/colors.ts rename to apps/web/src/lib/cards/colors.ts diff --git a/src/lib/cards/content/RSSFeedCard/RSSFeedCard.svelte b/apps/web/src/lib/cards/content/RSSFeedCard/RSSFeedCard.svelte similarity index 100% rename from src/lib/cards/content/RSSFeedCard/RSSFeedCard.svelte rename to apps/web/src/lib/cards/content/RSSFeedCard/RSSFeedCard.svelte diff --git a/src/lib/cards/content/RSSFeedCard/RSSFeedSettings.svelte b/apps/web/src/lib/cards/content/RSSFeedCard/RSSFeedSettings.svelte similarity index 100% rename from src/lib/cards/content/RSSFeedCard/RSSFeedSettings.svelte rename to apps/web/src/lib/cards/content/RSSFeedCard/RSSFeedSettings.svelte diff --git a/src/lib/cards/content/RSSFeedCard/api.remote.ts b/apps/web/src/lib/cards/content/RSSFeedCard/api.remote.ts similarity index 100% rename from src/lib/cards/content/RSSFeedCard/api.remote.ts rename to apps/web/src/lib/cards/content/RSSFeedCard/api.remote.ts diff --git a/src/lib/cards/content/RSSFeedCard/index.ts b/apps/web/src/lib/cards/content/RSSFeedCard/index.ts similarity index 100% rename from src/lib/cards/content/RSSFeedCard/index.ts rename to apps/web/src/lib/cards/content/RSSFeedCard/index.ts diff --git a/src/lib/cards/content/RSSFeedCard/types.ts b/apps/web/src/lib/cards/content/RSSFeedCard/types.ts similarity index 100% rename from src/lib/cards/content/RSSFeedCard/types.ts rename to apps/web/src/lib/cards/content/RSSFeedCard/types.ts diff --git a/src/lib/cards/content/RSSFeedCard/util.ts b/apps/web/src/lib/cards/content/RSSFeedCard/util.ts similarity index 100% rename from src/lib/cards/content/RSSFeedCard/util.ts rename to apps/web/src/lib/cards/content/RSSFeedCard/util.ts diff --git a/src/lib/cards/content/StandardSiteDocumentListCard/BlogEntry.svelte b/apps/web/src/lib/cards/content/StandardSiteDocumentListCard/BlogEntry.svelte similarity index 100% rename from src/lib/cards/content/StandardSiteDocumentListCard/BlogEntry.svelte rename to apps/web/src/lib/cards/content/StandardSiteDocumentListCard/BlogEntry.svelte diff --git a/src/lib/cards/content/StandardSiteDocumentListCard/DateTime.svelte b/apps/web/src/lib/cards/content/StandardSiteDocumentListCard/DateTime.svelte similarity index 100% rename from src/lib/cards/content/StandardSiteDocumentListCard/DateTime.svelte rename to apps/web/src/lib/cards/content/StandardSiteDocumentListCard/DateTime.svelte diff --git a/src/lib/cards/content/StandardSiteDocumentListCard/StandardSiteDocumentListCard.svelte b/apps/web/src/lib/cards/content/StandardSiteDocumentListCard/StandardSiteDocumentListCard.svelte similarity index 100% rename from src/lib/cards/content/StandardSiteDocumentListCard/StandardSiteDocumentListCard.svelte rename to apps/web/src/lib/cards/content/StandardSiteDocumentListCard/StandardSiteDocumentListCard.svelte diff --git a/src/lib/cards/content/StandardSiteDocumentListCard/index.ts b/apps/web/src/lib/cards/content/StandardSiteDocumentListCard/index.ts similarity index 100% rename from src/lib/cards/content/StandardSiteDocumentListCard/index.ts rename to apps/web/src/lib/cards/content/StandardSiteDocumentListCard/index.ts diff --git a/src/lib/cards/core/ImageCard/ImageCard.svelte b/apps/web/src/lib/cards/core/ImageCard/ImageCard.svelte similarity index 100% rename from src/lib/cards/core/ImageCard/ImageCard.svelte rename to apps/web/src/lib/cards/core/ImageCard/ImageCard.svelte diff --git a/src/lib/cards/core/ImageCard/ImageCardSettings.svelte b/apps/web/src/lib/cards/core/ImageCard/ImageCardSettings.svelte similarity index 100% rename from src/lib/cards/core/ImageCard/ImageCardSettings.svelte rename to apps/web/src/lib/cards/core/ImageCard/ImageCardSettings.svelte diff --git a/src/lib/cards/core/ImageCard/index.ts b/apps/web/src/lib/cards/core/ImageCard/index.ts similarity index 100% rename from src/lib/cards/core/ImageCard/index.ts rename to apps/web/src/lib/cards/core/ImageCard/index.ts diff --git a/src/lib/cards/core/LinkCard/CreateLinkCardModal.svelte b/apps/web/src/lib/cards/core/LinkCard/CreateLinkCardModal.svelte similarity index 100% rename from src/lib/cards/core/LinkCard/CreateLinkCardModal.svelte rename to apps/web/src/lib/cards/core/LinkCard/CreateLinkCardModal.svelte diff --git a/src/lib/cards/core/LinkCard/EditingLinkCard.svelte b/apps/web/src/lib/cards/core/LinkCard/EditingLinkCard.svelte similarity index 100% rename from src/lib/cards/core/LinkCard/EditingLinkCard.svelte rename to apps/web/src/lib/cards/core/LinkCard/EditingLinkCard.svelte diff --git a/src/lib/cards/core/LinkCard/LinkCard.svelte b/apps/web/src/lib/cards/core/LinkCard/LinkCard.svelte similarity index 100% rename from src/lib/cards/core/LinkCard/LinkCard.svelte rename to apps/web/src/lib/cards/core/LinkCard/LinkCard.svelte diff --git a/src/lib/cards/core/LinkCard/LinkCardSettings.svelte b/apps/web/src/lib/cards/core/LinkCard/LinkCardSettings.svelte similarity index 100% rename from src/lib/cards/core/LinkCard/LinkCardSettings.svelte rename to apps/web/src/lib/cards/core/LinkCard/LinkCardSettings.svelte diff --git a/src/lib/cards/core/LinkCard/index.ts b/apps/web/src/lib/cards/core/LinkCard/index.ts similarity index 100% rename from src/lib/cards/core/LinkCard/index.ts rename to apps/web/src/lib/cards/core/LinkCard/index.ts diff --git a/src/lib/cards/core/MapCard/CreateMapCardModal.svelte b/apps/web/src/lib/cards/core/MapCard/CreateMapCardModal.svelte similarity index 100% rename from src/lib/cards/core/MapCard/CreateMapCardModal.svelte rename to apps/web/src/lib/cards/core/MapCard/CreateMapCardModal.svelte diff --git a/src/lib/cards/core/MapCard/Map.svelte b/apps/web/src/lib/cards/core/MapCard/Map.svelte similarity index 100% rename from src/lib/cards/core/MapCard/Map.svelte rename to apps/web/src/lib/cards/core/MapCard/Map.svelte diff --git a/src/lib/cards/core/MapCard/MapCard.svelte b/apps/web/src/lib/cards/core/MapCard/MapCard.svelte similarity index 100% rename from src/lib/cards/core/MapCard/MapCard.svelte rename to apps/web/src/lib/cards/core/MapCard/MapCard.svelte diff --git a/src/lib/cards/core/MapCard/MapCardSettings.svelte b/apps/web/src/lib/cards/core/MapCard/MapCardSettings.svelte similarity index 100% rename from src/lib/cards/core/MapCard/MapCardSettings.svelte rename to apps/web/src/lib/cards/core/MapCard/MapCardSettings.svelte diff --git a/src/lib/cards/core/MapCard/index.ts b/apps/web/src/lib/cards/core/MapCard/index.ts similarity index 100% rename from src/lib/cards/core/MapCard/index.ts rename to apps/web/src/lib/cards/core/MapCard/index.ts diff --git a/src/lib/cards/core/SectionCard/EditingSectionCard.svelte b/apps/web/src/lib/cards/core/SectionCard/EditingSectionCard.svelte similarity index 100% rename from src/lib/cards/core/SectionCard/EditingSectionCard.svelte rename to apps/web/src/lib/cards/core/SectionCard/EditingSectionCard.svelte diff --git a/src/lib/cards/core/SectionCard/SectionCard.svelte b/apps/web/src/lib/cards/core/SectionCard/SectionCard.svelte similarity index 100% rename from src/lib/cards/core/SectionCard/SectionCard.svelte rename to apps/web/src/lib/cards/core/SectionCard/SectionCard.svelte diff --git a/src/lib/cards/core/SectionCard/SectionCardSettings.svelte b/apps/web/src/lib/cards/core/SectionCard/SectionCardSettings.svelte similarity index 100% rename from src/lib/cards/core/SectionCard/SectionCardSettings.svelte rename to apps/web/src/lib/cards/core/SectionCard/SectionCardSettings.svelte diff --git a/src/lib/cards/core/SectionCard/index.ts b/apps/web/src/lib/cards/core/SectionCard/index.ts similarity index 100% rename from src/lib/cards/core/SectionCard/index.ts rename to apps/web/src/lib/cards/core/SectionCard/index.ts diff --git a/src/lib/cards/core/SectionCard/styles.ts b/apps/web/src/lib/cards/core/SectionCard/styles.ts similarity index 100% rename from src/lib/cards/core/SectionCard/styles.ts rename to apps/web/src/lib/cards/core/SectionCard/styles.ts diff --git a/src/lib/cards/core/TextCard/EditingTextCard.svelte b/apps/web/src/lib/cards/core/TextCard/EditingTextCard.svelte similarity index 100% rename from src/lib/cards/core/TextCard/EditingTextCard.svelte rename to apps/web/src/lib/cards/core/TextCard/EditingTextCard.svelte diff --git a/src/lib/cards/core/TextCard/TextCard.svelte b/apps/web/src/lib/cards/core/TextCard/TextCard.svelte similarity index 100% rename from src/lib/cards/core/TextCard/TextCard.svelte rename to apps/web/src/lib/cards/core/TextCard/TextCard.svelte diff --git a/src/lib/cards/core/TextCard/TextCardSettings.svelte b/apps/web/src/lib/cards/core/TextCard/TextCardSettings.svelte similarity index 100% rename from src/lib/cards/core/TextCard/TextCardSettings.svelte rename to apps/web/src/lib/cards/core/TextCard/TextCardSettings.svelte diff --git a/src/lib/cards/core/TextCard/index.ts b/apps/web/src/lib/cards/core/TextCard/index.ts similarity index 100% rename from src/lib/cards/core/TextCard/index.ts rename to apps/web/src/lib/cards/core/TextCard/index.ts diff --git a/src/lib/cards/core/TextCard/styles.ts b/apps/web/src/lib/cards/core/TextCard/styles.ts similarity index 100% rename from src/lib/cards/core/TextCard/styles.ts rename to apps/web/src/lib/cards/core/TextCard/styles.ts diff --git a/src/lib/cards/games/DinoGameCard/DinoGameCard.svelte b/apps/web/src/lib/cards/games/DinoGameCard/DinoGameCard.svelte similarity index 100% rename from src/lib/cards/games/DinoGameCard/DinoGameCard.svelte rename to apps/web/src/lib/cards/games/DinoGameCard/DinoGameCard.svelte diff --git a/src/lib/cards/games/DinoGameCard/index.ts b/apps/web/src/lib/cards/games/DinoGameCard/index.ts similarity index 100% rename from src/lib/cards/games/DinoGameCard/index.ts rename to apps/web/src/lib/cards/games/DinoGameCard/index.ts diff --git a/src/lib/cards/games/TetrisCard/Tetris8Bit.mp3 b/apps/web/src/lib/cards/games/TetrisCard/Tetris8Bit.mp3 similarity index 100% rename from src/lib/cards/games/TetrisCard/Tetris8Bit.mp3 rename to apps/web/src/lib/cards/games/TetrisCard/Tetris8Bit.mp3 diff --git a/src/lib/cards/games/TetrisCard/TetrisCard.svelte b/apps/web/src/lib/cards/games/TetrisCard/TetrisCard.svelte similarity index 100% rename from src/lib/cards/games/TetrisCard/TetrisCard.svelte rename to apps/web/src/lib/cards/games/TetrisCard/TetrisCard.svelte diff --git a/src/lib/cards/games/TetrisCard/index.ts b/apps/web/src/lib/cards/games/TetrisCard/index.ts similarity index 100% rename from src/lib/cards/games/TetrisCard/index.ts rename to apps/web/src/lib/cards/games/TetrisCard/index.ts diff --git a/src/lib/cards/index.ts b/apps/web/src/lib/cards/index.ts similarity index 100% rename from src/lib/cards/index.ts rename to apps/web/src/lib/cards/index.ts diff --git a/src/lib/cards/media/AppleMusicCard/AppleMusicCard.svelte b/apps/web/src/lib/cards/media/AppleMusicCard/AppleMusicCard.svelte similarity index 100% rename from src/lib/cards/media/AppleMusicCard/AppleMusicCard.svelte rename to apps/web/src/lib/cards/media/AppleMusicCard/AppleMusicCard.svelte diff --git a/src/lib/cards/media/AppleMusicCard/CreateAppleMusicCardModal.svelte b/apps/web/src/lib/cards/media/AppleMusicCard/CreateAppleMusicCardModal.svelte similarity index 100% rename from src/lib/cards/media/AppleMusicCard/CreateAppleMusicCardModal.svelte rename to apps/web/src/lib/cards/media/AppleMusicCard/CreateAppleMusicCardModal.svelte diff --git a/src/lib/cards/media/AppleMusicCard/index.ts b/apps/web/src/lib/cards/media/AppleMusicCard/index.ts similarity index 100% rename from src/lib/cards/media/AppleMusicCard/index.ts rename to apps/web/src/lib/cards/media/AppleMusicCard/index.ts diff --git a/src/lib/cards/media/BandcampCard/BandcampCard.svelte b/apps/web/src/lib/cards/media/BandcampCard/BandcampCard.svelte similarity index 100% rename from src/lib/cards/media/BandcampCard/BandcampCard.svelte rename to apps/web/src/lib/cards/media/BandcampCard/BandcampCard.svelte diff --git a/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte b/apps/web/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte similarity index 100% rename from src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte rename to apps/web/src/lib/cards/media/BandcampCard/CreateBandcampCardModal.svelte diff --git a/src/lib/cards/media/BandcampCard/index.ts b/apps/web/src/lib/cards/media/BandcampCard/index.ts similarity index 100% rename from src/lib/cards/media/BandcampCard/index.ts rename to apps/web/src/lib/cards/media/BandcampCard/index.ts diff --git a/src/lib/cards/media/BandcampCard/shared.ts b/apps/web/src/lib/cards/media/BandcampCard/shared.ts similarity index 100% rename from src/lib/cards/media/BandcampCard/shared.ts rename to apps/web/src/lib/cards/media/BandcampCard/shared.ts diff --git a/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCard.svelte b/apps/web/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCard.svelte similarity index 100% rename from src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCard.svelte rename to apps/web/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCard.svelte diff --git a/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCardSettings.svelte b/apps/web/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCardSettings.svelte similarity index 100% rename from src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCardSettings.svelte rename to apps/web/src/lib/cards/media/BlueskyMediaCard/BlueskyMediaCardSettings.svelte diff --git a/src/lib/cards/media/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte b/apps/web/src/lib/cards/media/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte similarity index 100% rename from src/lib/cards/media/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte rename to apps/web/src/lib/cards/media/BlueskyMediaCard/CreateBlueskyMediaCardModal.svelte diff --git a/src/lib/cards/media/BlueskyMediaCard/Video.svelte b/apps/web/src/lib/cards/media/BlueskyMediaCard/Video.svelte similarity index 100% rename from src/lib/cards/media/BlueskyMediaCard/Video.svelte rename to apps/web/src/lib/cards/media/BlueskyMediaCard/Video.svelte diff --git a/src/lib/cards/media/BlueskyMediaCard/index.ts b/apps/web/src/lib/cards/media/BlueskyMediaCard/index.ts similarity index 100% rename from src/lib/cards/media/BlueskyMediaCard/index.ts rename to apps/web/src/lib/cards/media/BlueskyMediaCard/index.ts diff --git a/src/lib/cards/media/DerakkumaCards/DerakkumaCircleCard.svelte b/apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaCircleCard.svelte similarity index 100% rename from src/lib/cards/media/DerakkumaCards/DerakkumaCircleCard.svelte rename to apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaCircleCard.svelte diff --git a/src/lib/cards/media/DerakkumaCards/DerakkumaProfileCard.svelte b/apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaProfileCard.svelte similarity index 100% rename from src/lib/cards/media/DerakkumaCards/DerakkumaProfileCard.svelte rename to apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaProfileCard.svelte diff --git a/src/lib/cards/media/DerakkumaCards/DerakkumaScoresCard.svelte b/apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaScoresCard.svelte similarity index 100% rename from src/lib/cards/media/DerakkumaCards/DerakkumaScoresCard.svelte rename to apps/web/src/lib/cards/media/DerakkumaCards/DerakkumaScoresCard.svelte diff --git a/src/lib/cards/media/DerakkumaCards/index.ts b/apps/web/src/lib/cards/media/DerakkumaCards/index.ts similarity index 100% rename from src/lib/cards/media/DerakkumaCards/index.ts rename to apps/web/src/lib/cards/media/DerakkumaCards/index.ts diff --git a/src/lib/cards/media/DerakkumaCards/shared.ts b/apps/web/src/lib/cards/media/DerakkumaCards/shared.ts similarity index 100% rename from src/lib/cards/media/DerakkumaCards/shared.ts rename to apps/web/src/lib/cards/media/DerakkumaCards/shared.ts diff --git a/src/lib/cards/media/EmbedCard/CreateEmbedCardModal.svelte b/apps/web/src/lib/cards/media/EmbedCard/CreateEmbedCardModal.svelte similarity index 100% rename from src/lib/cards/media/EmbedCard/CreateEmbedCardModal.svelte rename to apps/web/src/lib/cards/media/EmbedCard/CreateEmbedCardModal.svelte diff --git a/src/lib/cards/media/EmbedCard/EmbedCard.svelte b/apps/web/src/lib/cards/media/EmbedCard/EmbedCard.svelte similarity index 100% rename from src/lib/cards/media/EmbedCard/EmbedCard.svelte rename to apps/web/src/lib/cards/media/EmbedCard/EmbedCard.svelte diff --git a/src/lib/cards/media/EmbedCard/index.ts b/apps/web/src/lib/cards/media/EmbedCard/index.ts similarity index 100% rename from src/lib/cards/media/EmbedCard/index.ts rename to apps/web/src/lib/cards/media/EmbedCard/index.ts diff --git a/src/lib/cards/media/GIFCard/CreateGifCardModal.svelte b/apps/web/src/lib/cards/media/GIFCard/CreateGifCardModal.svelte similarity index 100% rename from src/lib/cards/media/GIFCard/CreateGifCardModal.svelte rename to apps/web/src/lib/cards/media/GIFCard/CreateGifCardModal.svelte diff --git a/src/lib/cards/media/GIFCard/EditingGifCard.svelte b/apps/web/src/lib/cards/media/GIFCard/EditingGifCard.svelte similarity index 100% rename from src/lib/cards/media/GIFCard/EditingGifCard.svelte rename to apps/web/src/lib/cards/media/GIFCard/EditingGifCard.svelte diff --git a/src/lib/cards/media/GIFCard/GifCard.svelte b/apps/web/src/lib/cards/media/GIFCard/GifCard.svelte similarity index 100% rename from src/lib/cards/media/GIFCard/GifCard.svelte rename to apps/web/src/lib/cards/media/GIFCard/GifCard.svelte diff --git a/src/lib/cards/media/GIFCard/GifCardSettings.svelte b/apps/web/src/lib/cards/media/GIFCard/GifCardSettings.svelte similarity index 100% rename from src/lib/cards/media/GIFCard/GifCardSettings.svelte rename to apps/web/src/lib/cards/media/GIFCard/GifCardSettings.svelte diff --git a/src/lib/cards/media/GIFCard/GiphySearchModal.svelte b/apps/web/src/lib/cards/media/GIFCard/GiphySearchModal.svelte similarity index 96% rename from src/lib/cards/media/GIFCard/GiphySearchModal.svelte rename to apps/web/src/lib/cards/media/GIFCard/GiphySearchModal.svelte index 062a75a3..7bc1db5f 100644 --- a/src/lib/cards/media/GIFCard/GiphySearchModal.svelte +++ b/apps/web/src/lib/cards/media/GIFCard/GiphySearchModal.svelte @@ -47,7 +47,7 @@ isLoading = true; try { const url = new URL('https://api.giphy.com/v1/gifs/trending'); - url.searchParams.set('api_key', env.PUBLIC_GIPHY_API_TOKEN); + url.searchParams.set('api_key', env.PUBLIC_GIPHY_API_TOKEN ?? ''); url.searchParams.set('limit', '24'); url.searchParams.set('rating', 'g'); @@ -72,7 +72,7 @@ isLoading = true; try { const url = new URL('https://api.giphy.com/v1/gifs/search'); - url.searchParams.set('api_key', env.PUBLIC_GIPHY_API_TOKEN); + url.searchParams.set('api_key', env.PUBLIC_GIPHY_API_TOKEN ?? ''); url.searchParams.set('q', query); url.searchParams.set('limit', '24'); url.searchParams.set('rating', 'g'); diff --git a/src/lib/cards/media/GIFCard/PoweredByGiphy.gif b/apps/web/src/lib/cards/media/GIFCard/PoweredByGiphy.gif similarity index 100% rename from src/lib/cards/media/GIFCard/PoweredByGiphy.gif rename to apps/web/src/lib/cards/media/GIFCard/PoweredByGiphy.gif diff --git a/src/lib/cards/media/GIFCard/index.ts b/apps/web/src/lib/cards/media/GIFCard/index.ts similarity index 100% rename from src/lib/cards/media/GIFCard/index.ts rename to apps/web/src/lib/cards/media/GIFCard/index.ts diff --git a/src/lib/cards/media/LastFMCard/CreateLastFMCardModal.svelte b/apps/web/src/lib/cards/media/LastFMCard/CreateLastFMCardModal.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/CreateLastFMCardModal.svelte rename to apps/web/src/lib/cards/media/LastFMCard/CreateLastFMCardModal.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMAlbumArt.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMAlbumArt.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMAlbumArt.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMAlbumArt.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMPeriodSettings.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMPeriodSettings.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMPeriodSettings.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMPeriodSettings.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMProfileCard/LastFMProfileCard.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMProfileCard/LastFMProfileCard.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMProfileCard/LastFMProfileCard.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMProfileCard/LastFMProfileCard.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMProfileCard/index.ts b/apps/web/src/lib/cards/media/LastFMCard/LastFMProfileCard/index.ts similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMProfileCard/index.ts rename to apps/web/src/lib/cards/media/LastFMCard/LastFMProfileCard/index.ts diff --git a/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/LastFMRecentTracksCard.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/LastFMRecentTracksCard.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/LastFMRecentTracksCard.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/LastFMRecentTracksCard.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/index.ts b/apps/web/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/index.ts similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/index.ts rename to apps/web/src/lib/cards/media/LastFMCard/LastFMRecentTracksCard/index.ts diff --git a/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCard.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCard.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCard.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCard.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCardSettings.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCardSettings.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCardSettings.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/LastFMTopAlbumsCardSettings.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/index.ts b/apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/index.ts similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/index.ts rename to apps/web/src/lib/cards/media/LastFMCard/LastFMTopAlbumsCard/index.ts diff --git a/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/LastFMTopTracksCard.svelte b/apps/web/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/LastFMTopTracksCard.svelte similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMTopTracksCard/LastFMTopTracksCard.svelte rename to apps/web/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/LastFMTopTracksCard.svelte diff --git a/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/index.ts b/apps/web/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/index.ts similarity index 100% rename from src/lib/cards/media/LastFMCard/LastFMTopTracksCard/index.ts rename to apps/web/src/lib/cards/media/LastFMCard/LastFMTopTracksCard/index.ts diff --git a/src/lib/cards/media/LastFMCard/api.remote.ts b/apps/web/src/lib/cards/media/LastFMCard/api.remote.ts similarity index 100% rename from src/lib/cards/media/LastFMCard/api.remote.ts rename to apps/web/src/lib/cards/media/LastFMCard/api.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/CoverArt.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/CoverArt.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/CoverArt.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/CoverArt.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/CreateListenBrainzCardModal.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/CreateListenBrainzCardModal.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/CreateListenBrainzCardModal.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/CreateListenBrainzCardModal.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/ListenBrainzNowPlayingCard.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/ListenBrainzNowPlayingCard.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/ListenBrainzNowPlayingCard.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/ListenBrainzNowPlayingCard.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/index.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/index.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/index.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/index.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/nowplaying.remote.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/nowplaying.remote.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/nowplaying.remote.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzNowPlayingCard/nowplaying.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/ListenBrainzRecentListensCard.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/ListenBrainzRecentListensCard.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/ListenBrainzRecentListensCard.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/ListenBrainzRecentListensCard.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/index.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/index.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/index.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/index.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/listens.remote.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/listens.remote.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/listens.remote.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzRecentListensCard/listens.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/ListenBrainzTopAlbumsCard.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/ListenBrainzTopAlbumsCard.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/ListenBrainzTopAlbumsCard.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/ListenBrainzTopAlbumsCard.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/albums.remote.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/albums.remote.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/albums.remote.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/albums.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/index.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/index.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/index.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopAlbumsCard/index.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/ListenBrainzTopArtistsCard.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/ListenBrainzTopArtistsCard.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/ListenBrainzTopArtistsCard.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/ListenBrainzTopArtistsCard.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/artists.remote.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/artists.remote.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/artists.remote.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/artists.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/index.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/index.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/index.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopArtistsCard/index.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/ListenBrainzTopSongsCard.svelte b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/ListenBrainzTopSongsCard.svelte similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/ListenBrainzTopSongsCard.svelte rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/ListenBrainzTopSongsCard.svelte diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/index.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/index.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/index.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/index.ts diff --git a/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/recordings.remote.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/recordings.remote.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/recordings.remote.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/ListenBrainzTopSongsCard/recordings.remote.ts diff --git a/src/lib/cards/media/ListenBrainzCard/shared.server.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/shared.server.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/shared.server.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/shared.server.ts diff --git a/src/lib/cards/media/ListenBrainzCard/types.ts b/apps/web/src/lib/cards/media/ListenBrainzCard/types.ts similarity index 100% rename from src/lib/cards/media/ListenBrainzCard/types.ts rename to apps/web/src/lib/cards/media/ListenBrainzCard/types.ts diff --git a/src/lib/cards/media/LivestreamCard/Icon.svelte b/apps/web/src/lib/cards/media/LivestreamCard/Icon.svelte similarity index 100% rename from src/lib/cards/media/LivestreamCard/Icon.svelte rename to apps/web/src/lib/cards/media/LivestreamCard/Icon.svelte diff --git a/src/lib/cards/media/LivestreamCard/LivestreamCard.svelte b/apps/web/src/lib/cards/media/LivestreamCard/LivestreamCard.svelte similarity index 100% rename from src/lib/cards/media/LivestreamCard/LivestreamCard.svelte rename to apps/web/src/lib/cards/media/LivestreamCard/LivestreamCard.svelte diff --git a/src/lib/cards/media/LivestreamCard/LivestreamEmbedCard.svelte b/apps/web/src/lib/cards/media/LivestreamCard/LivestreamEmbedCard.svelte similarity index 100% rename from src/lib/cards/media/LivestreamCard/LivestreamEmbedCard.svelte rename to apps/web/src/lib/cards/media/LivestreamCard/LivestreamEmbedCard.svelte diff --git a/src/lib/cards/media/LivestreamCard/index.ts b/apps/web/src/lib/cards/media/LivestreamCard/index.ts similarity index 100% rename from src/lib/cards/media/LivestreamCard/index.ts rename to apps/web/src/lib/cards/media/LivestreamCard/index.ts diff --git a/src/lib/cards/media/PhotoGalleryCard/CreateGrainGalleryCardModal.svelte b/apps/web/src/lib/cards/media/PhotoGalleryCard/CreateGrainGalleryCardModal.svelte similarity index 100% rename from src/lib/cards/media/PhotoGalleryCard/CreateGrainGalleryCardModal.svelte rename to apps/web/src/lib/cards/media/PhotoGalleryCard/CreateGrainGalleryCardModal.svelte diff --git a/src/lib/cards/media/PhotoGalleryCard/PhotoGalleryCard.svelte b/apps/web/src/lib/cards/media/PhotoGalleryCard/PhotoGalleryCard.svelte similarity index 100% rename from src/lib/cards/media/PhotoGalleryCard/PhotoGalleryCard.svelte rename to apps/web/src/lib/cards/media/PhotoGalleryCard/PhotoGalleryCard.svelte diff --git a/src/lib/cards/media/PhotoGalleryCard/helpers.ts b/apps/web/src/lib/cards/media/PhotoGalleryCard/helpers.ts similarity index 100% rename from src/lib/cards/media/PhotoGalleryCard/helpers.ts rename to apps/web/src/lib/cards/media/PhotoGalleryCard/helpers.ts diff --git a/src/lib/cards/media/PhotoGalleryCard/index.ts b/apps/web/src/lib/cards/media/PhotoGalleryCard/index.ts similarity index 100% rename from src/lib/cards/media/PhotoGalleryCard/index.ts rename to apps/web/src/lib/cards/media/PhotoGalleryCard/index.ts diff --git a/src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte b/apps/web/src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte similarity index 100% rename from src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte rename to apps/web/src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte diff --git a/src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte b/apps/web/src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte similarity index 100% rename from src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte rename to apps/web/src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte diff --git a/src/lib/cards/media/PlyrFMCard/index.ts b/apps/web/src/lib/cards/media/PlyrFMCard/index.ts similarity index 100% rename from src/lib/cards/media/PlyrFMCard/index.ts rename to apps/web/src/lib/cards/media/PlyrFMCard/index.ts diff --git a/src/lib/cards/media/PopfeedReviews/PopfeedReviewsCard.svelte b/apps/web/src/lib/cards/media/PopfeedReviews/PopfeedReviewsCard.svelte similarity index 100% rename from src/lib/cards/media/PopfeedReviews/PopfeedReviewsCard.svelte rename to apps/web/src/lib/cards/media/PopfeedReviews/PopfeedReviewsCard.svelte diff --git a/src/lib/cards/media/PopfeedReviews/Rating.svelte b/apps/web/src/lib/cards/media/PopfeedReviews/Rating.svelte similarity index 100% rename from src/lib/cards/media/PopfeedReviews/Rating.svelte rename to apps/web/src/lib/cards/media/PopfeedReviews/Rating.svelte diff --git a/src/lib/cards/media/PopfeedReviews/index.ts b/apps/web/src/lib/cards/media/PopfeedReviews/index.ts similarity index 100% rename from src/lib/cards/media/PopfeedReviews/index.ts rename to apps/web/src/lib/cards/media/PopfeedReviews/index.ts diff --git a/src/lib/cards/media/RockskyPlaysCard/AlbumArt.svelte b/apps/web/src/lib/cards/media/RockskyPlaysCard/AlbumArt.svelte similarity index 100% rename from src/lib/cards/media/RockskyPlaysCard/AlbumArt.svelte rename to apps/web/src/lib/cards/media/RockskyPlaysCard/AlbumArt.svelte diff --git a/src/lib/cards/media/RockskyPlaysCard/RockskyPlaysCard.svelte b/apps/web/src/lib/cards/media/RockskyPlaysCard/RockskyPlaysCard.svelte similarity index 100% rename from src/lib/cards/media/RockskyPlaysCard/RockskyPlaysCard.svelte rename to apps/web/src/lib/cards/media/RockskyPlaysCard/RockskyPlaysCard.svelte diff --git a/src/lib/cards/media/RockskyPlaysCard/index.ts b/apps/web/src/lib/cards/media/RockskyPlaysCard/index.ts similarity index 100% rename from src/lib/cards/media/RockskyPlaysCard/index.ts rename to apps/web/src/lib/cards/media/RockskyPlaysCard/index.ts diff --git a/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte b/apps/web/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte similarity index 100% rename from src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte rename to apps/web/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte diff --git a/src/lib/cards/media/SecretImageCard/SecretImageCard.svelte b/apps/web/src/lib/cards/media/SecretImageCard/SecretImageCard.svelte similarity index 100% rename from src/lib/cards/media/SecretImageCard/SecretImageCard.svelte rename to apps/web/src/lib/cards/media/SecretImageCard/SecretImageCard.svelte diff --git a/src/lib/cards/media/SecretImageCard/SecretImageCardSettings.svelte b/apps/web/src/lib/cards/media/SecretImageCard/SecretImageCardSettings.svelte similarity index 100% rename from src/lib/cards/media/SecretImageCard/SecretImageCardSettings.svelte rename to apps/web/src/lib/cards/media/SecretImageCard/SecretImageCardSettings.svelte diff --git a/src/lib/cards/media/SecretImageCard/crypto.ts b/apps/web/src/lib/cards/media/SecretImageCard/crypto.ts similarity index 100% rename from src/lib/cards/media/SecretImageCard/crypto.ts rename to apps/web/src/lib/cards/media/SecretImageCard/crypto.ts diff --git a/src/lib/cards/media/SecretImageCard/index.ts b/apps/web/src/lib/cards/media/SecretImageCard/index.ts similarity index 100% rename from src/lib/cards/media/SecretImageCard/index.ts rename to apps/web/src/lib/cards/media/SecretImageCard/index.ts diff --git a/src/lib/cards/media/SoundCloudCard/CreateSoundCloudCardModal.svelte b/apps/web/src/lib/cards/media/SoundCloudCard/CreateSoundCloudCardModal.svelte similarity index 100% rename from src/lib/cards/media/SoundCloudCard/CreateSoundCloudCardModal.svelte rename to apps/web/src/lib/cards/media/SoundCloudCard/CreateSoundCloudCardModal.svelte diff --git a/src/lib/cards/media/SoundCloudCard/SoundCloudCard.svelte b/apps/web/src/lib/cards/media/SoundCloudCard/SoundCloudCard.svelte similarity index 100% rename from src/lib/cards/media/SoundCloudCard/SoundCloudCard.svelte rename to apps/web/src/lib/cards/media/SoundCloudCard/SoundCloudCard.svelte diff --git a/src/lib/cards/media/SoundCloudCard/index.ts b/apps/web/src/lib/cards/media/SoundCloudCard/index.ts similarity index 100% rename from src/lib/cards/media/SoundCloudCard/index.ts rename to apps/web/src/lib/cards/media/SoundCloudCard/index.ts diff --git a/src/lib/cards/media/SpotifyCard/CreateSpotifyCardModal.svelte b/apps/web/src/lib/cards/media/SpotifyCard/CreateSpotifyCardModal.svelte similarity index 100% rename from src/lib/cards/media/SpotifyCard/CreateSpotifyCardModal.svelte rename to apps/web/src/lib/cards/media/SpotifyCard/CreateSpotifyCardModal.svelte diff --git a/src/lib/cards/media/SpotifyCard/SpotifyCard.svelte b/apps/web/src/lib/cards/media/SpotifyCard/SpotifyCard.svelte similarity index 100% rename from src/lib/cards/media/SpotifyCard/SpotifyCard.svelte rename to apps/web/src/lib/cards/media/SpotifyCard/SpotifyCard.svelte diff --git a/src/lib/cards/media/SpotifyCard/index.ts b/apps/web/src/lib/cards/media/SpotifyCard/index.ts similarity index 100% rename from src/lib/cards/media/SpotifyCard/index.ts rename to apps/web/src/lib/cards/media/SpotifyCard/index.ts diff --git a/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte b/apps/web/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte similarity index 100% rename from src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte rename to apps/web/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte diff --git a/src/lib/cards/media/StatusphereCard/SettingsStatusphereCard.svelte b/apps/web/src/lib/cards/media/StatusphereCard/SettingsStatusphereCard.svelte similarity index 100% rename from src/lib/cards/media/StatusphereCard/SettingsStatusphereCard.svelte rename to apps/web/src/lib/cards/media/StatusphereCard/SettingsStatusphereCard.svelte diff --git a/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte b/apps/web/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte similarity index 100% rename from src/lib/cards/media/StatusphereCard/StatusphereCard.svelte rename to apps/web/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte diff --git a/src/lib/cards/media/StatusphereCard/icons.json b/apps/web/src/lib/cards/media/StatusphereCard/icons.json similarity index 100% rename from src/lib/cards/media/StatusphereCard/icons.json rename to apps/web/src/lib/cards/media/StatusphereCard/icons.json diff --git a/src/lib/cards/media/StatusphereCard/index.ts b/apps/web/src/lib/cards/media/StatusphereCard/index.ts similarity index 100% rename from src/lib/cards/media/StatusphereCard/index.ts rename to apps/web/src/lib/cards/media/StatusphereCard/index.ts diff --git a/src/lib/cards/media/TealFMPlaysCard/AlbumArt.svelte b/apps/web/src/lib/cards/media/TealFMPlaysCard/AlbumArt.svelte similarity index 100% rename from src/lib/cards/media/TealFMPlaysCard/AlbumArt.svelte rename to apps/web/src/lib/cards/media/TealFMPlaysCard/AlbumArt.svelte diff --git a/src/lib/cards/media/TealFMPlaysCard/TealFMPlaysCard.svelte b/apps/web/src/lib/cards/media/TealFMPlaysCard/TealFMPlaysCard.svelte similarity index 100% rename from src/lib/cards/media/TealFMPlaysCard/TealFMPlaysCard.svelte rename to apps/web/src/lib/cards/media/TealFMPlaysCard/TealFMPlaysCard.svelte diff --git a/src/lib/cards/media/TealFMPlaysCard/index.ts b/apps/web/src/lib/cards/media/TealFMPlaysCard/index.ts similarity index 100% rename from src/lib/cards/media/TealFMPlaysCard/index.ts rename to apps/web/src/lib/cards/media/TealFMPlaysCard/index.ts diff --git a/src/lib/cards/media/VideoCard/CreateVideoCardModal.svelte b/apps/web/src/lib/cards/media/VideoCard/CreateVideoCardModal.svelte similarity index 100% rename from src/lib/cards/media/VideoCard/CreateVideoCardModal.svelte rename to apps/web/src/lib/cards/media/VideoCard/CreateVideoCardModal.svelte diff --git a/src/lib/cards/media/VideoCard/VideoCard.svelte b/apps/web/src/lib/cards/media/VideoCard/VideoCard.svelte similarity index 100% rename from src/lib/cards/media/VideoCard/VideoCard.svelte rename to apps/web/src/lib/cards/media/VideoCard/VideoCard.svelte diff --git a/src/lib/cards/media/VideoCard/index.ts b/apps/web/src/lib/cards/media/VideoCard/index.ts similarity index 100% rename from src/lib/cards/media/VideoCard/index.ts rename to apps/web/src/lib/cards/media/VideoCard/index.ts diff --git a/src/lib/cards/media/VideoCard/upload.ts b/apps/web/src/lib/cards/media/VideoCard/upload.ts similarity index 100% rename from src/lib/cards/media/VideoCard/upload.ts rename to apps/web/src/lib/cards/media/VideoCard/upload.ts diff --git a/src/lib/cards/media/YoutubeVideoCard/CreateYoutubeCardModal.svelte b/apps/web/src/lib/cards/media/YoutubeVideoCard/CreateYoutubeCardModal.svelte similarity index 100% rename from src/lib/cards/media/YoutubeVideoCard/CreateYoutubeCardModal.svelte rename to apps/web/src/lib/cards/media/YoutubeVideoCard/CreateYoutubeCardModal.svelte diff --git a/src/lib/cards/media/YoutubeVideoCard/YoutubeCard.svelte b/apps/web/src/lib/cards/media/YoutubeVideoCard/YoutubeCard.svelte similarity index 100% rename from src/lib/cards/media/YoutubeVideoCard/YoutubeCard.svelte rename to apps/web/src/lib/cards/media/YoutubeVideoCard/YoutubeCard.svelte diff --git a/src/lib/cards/media/YoutubeVideoCard/YoutubeCardSettings.svelte b/apps/web/src/lib/cards/media/YoutubeVideoCard/YoutubeCardSettings.svelte similarity index 100% rename from src/lib/cards/media/YoutubeVideoCard/YoutubeCardSettings.svelte rename to apps/web/src/lib/cards/media/YoutubeVideoCard/YoutubeCardSettings.svelte diff --git a/src/lib/cards/media/YoutubeVideoCard/index.ts b/apps/web/src/lib/cards/media/YoutubeVideoCard/index.ts similarity index 100% rename from src/lib/cards/media/YoutubeVideoCard/index.ts rename to apps/web/src/lib/cards/media/YoutubeVideoCard/index.ts diff --git a/src/lib/cards/social/ATProtoCollectionsCard/ATProtoCollectionsCard.svelte b/apps/web/src/lib/cards/social/ATProtoCollectionsCard/ATProtoCollectionsCard.svelte similarity index 100% rename from src/lib/cards/social/ATProtoCollectionsCard/ATProtoCollectionsCard.svelte rename to apps/web/src/lib/cards/social/ATProtoCollectionsCard/ATProtoCollectionsCard.svelte diff --git a/src/lib/cards/social/ATProtoCollectionsCard/index.ts b/apps/web/src/lib/cards/social/ATProtoCollectionsCard/index.ts similarity index 100% rename from src/lib/cards/social/ATProtoCollectionsCard/index.ts rename to apps/web/src/lib/cards/social/ATProtoCollectionsCard/index.ts diff --git a/src/lib/cards/social/BaserowFormCard/BaserowFormCard.svelte b/apps/web/src/lib/cards/social/BaserowFormCard/BaserowFormCard.svelte similarity index 100% rename from src/lib/cards/social/BaserowFormCard/BaserowFormCard.svelte rename to apps/web/src/lib/cards/social/BaserowFormCard/BaserowFormCard.svelte diff --git a/src/lib/cards/social/BaserowFormCard/CreateBaserowFormCardModal.svelte b/apps/web/src/lib/cards/social/BaserowFormCard/CreateBaserowFormCardModal.svelte similarity index 100% rename from src/lib/cards/social/BaserowFormCard/CreateBaserowFormCardModal.svelte rename to apps/web/src/lib/cards/social/BaserowFormCard/CreateBaserowFormCardModal.svelte diff --git a/src/lib/cards/social/BaserowFormCard/baserow.ts b/apps/web/src/lib/cards/social/BaserowFormCard/baserow.ts similarity index 100% rename from src/lib/cards/social/BaserowFormCard/baserow.ts rename to apps/web/src/lib/cards/social/BaserowFormCard/baserow.ts diff --git a/src/lib/cards/social/BaserowFormCard/index.ts b/apps/web/src/lib/cards/social/BaserowFormCard/index.ts similarity index 100% rename from src/lib/cards/social/BaserowFormCard/index.ts rename to apps/web/src/lib/cards/social/BaserowFormCard/index.ts diff --git a/src/lib/cards/social/BigSocialCard/BigSocialCard.svelte b/apps/web/src/lib/cards/social/BigSocialCard/BigSocialCard.svelte similarity index 100% rename from src/lib/cards/social/BigSocialCard/BigSocialCard.svelte rename to apps/web/src/lib/cards/social/BigSocialCard/BigSocialCard.svelte diff --git a/src/lib/cards/social/BigSocialCard/CreateBigSocialCardModal.svelte b/apps/web/src/lib/cards/social/BigSocialCard/CreateBigSocialCardModal.svelte similarity index 100% rename from src/lib/cards/social/BigSocialCard/CreateBigSocialCardModal.svelte rename to apps/web/src/lib/cards/social/BigSocialCard/CreateBigSocialCardModal.svelte diff --git a/src/lib/cards/social/BigSocialCard/index.ts b/apps/web/src/lib/cards/social/BigSocialCard/index.ts similarity index 100% rename from src/lib/cards/social/BigSocialCard/index.ts rename to apps/web/src/lib/cards/social/BigSocialCard/index.ts diff --git a/src/lib/cards/social/BlueskyFeedCard/BlueskyFeedCard.svelte b/apps/web/src/lib/cards/social/BlueskyFeedCard/BlueskyFeedCard.svelte similarity index 100% rename from src/lib/cards/social/BlueskyFeedCard/BlueskyFeedCard.svelte rename to apps/web/src/lib/cards/social/BlueskyFeedCard/BlueskyFeedCard.svelte diff --git a/src/lib/cards/social/BlueskyFeedCard/index.ts b/apps/web/src/lib/cards/social/BlueskyFeedCard/index.ts similarity index 100% rename from src/lib/cards/social/BlueskyFeedCard/index.ts rename to apps/web/src/lib/cards/social/BlueskyFeedCard/index.ts diff --git a/src/lib/cards/social/BlueskyPostCard/BlueskyPostCard.svelte b/apps/web/src/lib/cards/social/BlueskyPostCard/BlueskyPostCard.svelte similarity index 100% rename from src/lib/cards/social/BlueskyPostCard/BlueskyPostCard.svelte rename to apps/web/src/lib/cards/social/BlueskyPostCard/BlueskyPostCard.svelte diff --git a/src/lib/cards/social/BlueskyPostCard/CreateBlueskyPostCardModal.svelte b/apps/web/src/lib/cards/social/BlueskyPostCard/CreateBlueskyPostCardModal.svelte similarity index 100% rename from src/lib/cards/social/BlueskyPostCard/CreateBlueskyPostCardModal.svelte rename to apps/web/src/lib/cards/social/BlueskyPostCard/CreateBlueskyPostCardModal.svelte diff --git a/src/lib/cards/social/BlueskyPostCard/index.ts b/apps/web/src/lib/cards/social/BlueskyPostCard/index.ts similarity index 100% rename from src/lib/cards/social/BlueskyPostCard/index.ts rename to apps/web/src/lib/cards/social/BlueskyPostCard/index.ts diff --git a/src/lib/cards/social/BlueskyPostCard/utils.ts b/apps/web/src/lib/cards/social/BlueskyPostCard/utils.ts similarity index 100% rename from src/lib/cards/social/BlueskyPostCard/utils.ts rename to apps/web/src/lib/cards/social/BlueskyPostCard/utils.ts diff --git a/src/lib/cards/social/BlueskyProfileCard/BlueskyProfileCard.svelte b/apps/web/src/lib/cards/social/BlueskyProfileCard/BlueskyProfileCard.svelte similarity index 100% rename from src/lib/cards/social/BlueskyProfileCard/BlueskyProfileCard.svelte rename to apps/web/src/lib/cards/social/BlueskyProfileCard/BlueskyProfileCard.svelte diff --git a/src/lib/cards/social/BlueskyProfileCard/index.ts b/apps/web/src/lib/cards/social/BlueskyProfileCard/index.ts similarity index 100% rename from src/lib/cards/social/BlueskyProfileCard/index.ts rename to apps/web/src/lib/cards/social/BlueskyProfileCard/index.ts diff --git a/src/lib/cards/social/BufoStatusCard/BufoStatusCard.svelte b/apps/web/src/lib/cards/social/BufoStatusCard/BufoStatusCard.svelte similarity index 100% rename from src/lib/cards/social/BufoStatusCard/BufoStatusCard.svelte rename to apps/web/src/lib/cards/social/BufoStatusCard/BufoStatusCard.svelte diff --git a/src/lib/cards/social/BufoStatusCard/index.ts b/apps/web/src/lib/cards/social/BufoStatusCard/index.ts similarity index 100% rename from src/lib/cards/social/BufoStatusCard/index.ts rename to apps/web/src/lib/cards/social/BufoStatusCard/index.ts diff --git a/src/lib/cards/social/ButtondownCard/ButtondownCard.svelte b/apps/web/src/lib/cards/social/ButtondownCard/ButtondownCard.svelte similarity index 100% rename from src/lib/cards/social/ButtondownCard/ButtondownCard.svelte rename to apps/web/src/lib/cards/social/ButtondownCard/ButtondownCard.svelte diff --git a/src/lib/cards/social/ButtondownCard/ButtondownCardSettings.svelte b/apps/web/src/lib/cards/social/ButtondownCard/ButtondownCardSettings.svelte similarity index 100% rename from src/lib/cards/social/ButtondownCard/ButtondownCardSettings.svelte rename to apps/web/src/lib/cards/social/ButtondownCard/ButtondownCardSettings.svelte diff --git a/src/lib/cards/social/ButtondownCard/index.ts b/apps/web/src/lib/cards/social/ButtondownCard/index.ts similarity index 100% rename from src/lib/cards/social/ButtondownCard/index.ts rename to apps/web/src/lib/cards/social/ButtondownCard/index.ts diff --git a/src/lib/cards/social/EventCard/CreateEventCardModal.svelte b/apps/web/src/lib/cards/social/EventCard/CreateEventCardModal.svelte similarity index 100% rename from src/lib/cards/social/EventCard/CreateEventCardModal.svelte rename to apps/web/src/lib/cards/social/EventCard/CreateEventCardModal.svelte diff --git a/src/lib/cards/social/EventCard/EventCard.svelte b/apps/web/src/lib/cards/social/EventCard/EventCard.svelte similarity index 100% rename from src/lib/cards/social/EventCard/EventCard.svelte rename to apps/web/src/lib/cards/social/EventCard/EventCard.svelte diff --git a/src/lib/cards/social/EventCard/index.ts b/apps/web/src/lib/cards/social/EventCard/index.ts similarity index 100% rename from src/lib/cards/social/EventCard/index.ts rename to apps/web/src/lib/cards/social/EventCard/index.ts diff --git a/src/lib/cards/social/FriendsCard/FriendsCard.svelte b/apps/web/src/lib/cards/social/FriendsCard/FriendsCard.svelte similarity index 100% rename from src/lib/cards/social/FriendsCard/FriendsCard.svelte rename to apps/web/src/lib/cards/social/FriendsCard/FriendsCard.svelte diff --git a/src/lib/cards/social/FriendsCard/FriendsCardSettings.svelte b/apps/web/src/lib/cards/social/FriendsCard/FriendsCardSettings.svelte similarity index 100% rename from src/lib/cards/social/FriendsCard/FriendsCardSettings.svelte rename to apps/web/src/lib/cards/social/FriendsCard/FriendsCardSettings.svelte diff --git a/src/lib/cards/social/FriendsCard/index.ts b/apps/web/src/lib/cards/social/FriendsCard/index.ts similarity index 100% rename from src/lib/cards/social/FriendsCard/index.ts rename to apps/web/src/lib/cards/social/FriendsCard/index.ts diff --git a/src/lib/cards/social/GermDMCard/GermDMCard.svelte b/apps/web/src/lib/cards/social/GermDMCard/GermDMCard.svelte similarity index 100% rename from src/lib/cards/social/GermDMCard/GermDMCard.svelte rename to apps/web/src/lib/cards/social/GermDMCard/GermDMCard.svelte diff --git a/src/lib/cards/social/GermDMCard/index.ts b/apps/web/src/lib/cards/social/GermDMCard/index.ts similarity index 100% rename from src/lib/cards/social/GermDMCard/index.ts rename to apps/web/src/lib/cards/social/GermDMCard/index.ts diff --git a/src/lib/cards/social/GitHubContributorsCard/CreateGitHubContributorsCardModal.svelte b/apps/web/src/lib/cards/social/GitHubContributorsCard/CreateGitHubContributorsCardModal.svelte similarity index 100% rename from src/lib/cards/social/GitHubContributorsCard/CreateGitHubContributorsCardModal.svelte rename to apps/web/src/lib/cards/social/GitHubContributorsCard/CreateGitHubContributorsCardModal.svelte diff --git a/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCard.svelte b/apps/web/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCard.svelte similarity index 100% rename from src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCard.svelte rename to apps/web/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCard.svelte diff --git a/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCardSettings.svelte b/apps/web/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCardSettings.svelte similarity index 100% rename from src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCardSettings.svelte rename to apps/web/src/lib/cards/social/GitHubContributorsCard/GitHubContributorsCardSettings.svelte diff --git a/src/lib/cards/social/GitHubContributorsCard/api.remote.ts b/apps/web/src/lib/cards/social/GitHubContributorsCard/api.remote.ts similarity index 100% rename from src/lib/cards/social/GitHubContributorsCard/api.remote.ts rename to apps/web/src/lib/cards/social/GitHubContributorsCard/api.remote.ts diff --git a/src/lib/cards/social/GitHubContributorsCard/index.ts b/apps/web/src/lib/cards/social/GitHubContributorsCard/index.ts similarity index 100% rename from src/lib/cards/social/GitHubContributorsCard/index.ts rename to apps/web/src/lib/cards/social/GitHubContributorsCard/index.ts diff --git a/src/lib/cards/social/GitHubProfileCard/CreateGitHubProfileCardModal.svelte b/apps/web/src/lib/cards/social/GitHubProfileCard/CreateGitHubProfileCardModal.svelte similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/CreateGitHubProfileCardModal.svelte rename to apps/web/src/lib/cards/social/GitHubProfileCard/CreateGitHubProfileCardModal.svelte diff --git a/src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte b/apps/web/src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte rename to apps/web/src/lib/cards/social/GitHubProfileCard/GitHubProfileCard.svelte diff --git a/src/lib/cards/social/GitHubProfileCard/GithubContributionsGraph.svelte b/apps/web/src/lib/cards/social/GitHubProfileCard/GithubContributionsGraph.svelte similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/GithubContributionsGraph.svelte rename to apps/web/src/lib/cards/social/GitHubProfileCard/GithubContributionsGraph.svelte diff --git a/src/lib/cards/social/GitHubProfileCard/api.remote.ts b/apps/web/src/lib/cards/social/GitHubProfileCard/api.remote.ts similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/api.remote.ts rename to apps/web/src/lib/cards/social/GitHubProfileCard/api.remote.ts diff --git a/src/lib/cards/social/GitHubProfileCard/index.ts b/apps/web/src/lib/cards/social/GitHubProfileCard/index.ts similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/index.ts rename to apps/web/src/lib/cards/social/GitHubProfileCard/index.ts diff --git a/src/lib/cards/social/GitHubProfileCard/types.ts b/apps/web/src/lib/cards/social/GitHubProfileCard/types.ts similarity index 100% rename from src/lib/cards/social/GitHubProfileCard/types.ts rename to apps/web/src/lib/cards/social/GitHubProfileCard/types.ts diff --git a/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte b/apps/web/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte similarity index 100% rename from src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte rename to apps/web/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte diff --git a/src/lib/cards/social/GuestbookCard/GuestbookCard.svelte b/apps/web/src/lib/cards/social/GuestbookCard/GuestbookCard.svelte similarity index 100% rename from src/lib/cards/social/GuestbookCard/GuestbookCard.svelte rename to apps/web/src/lib/cards/social/GuestbookCard/GuestbookCard.svelte diff --git a/src/lib/cards/social/GuestbookCard/index.ts b/apps/web/src/lib/cards/social/GuestbookCard/index.ts similarity index 100% rename from src/lib/cards/social/GuestbookCard/index.ts rename to apps/web/src/lib/cards/social/GuestbookCard/index.ts diff --git a/src/lib/cards/social/KichCookingLogCard/KichCookingLogCard.svelte b/apps/web/src/lib/cards/social/KichCookingLogCard/KichCookingLogCard.svelte similarity index 100% rename from src/lib/cards/social/KichCookingLogCard/KichCookingLogCard.svelte rename to apps/web/src/lib/cards/social/KichCookingLogCard/KichCookingLogCard.svelte diff --git a/src/lib/cards/social/KichCookingLogCard/index.ts b/apps/web/src/lib/cards/social/KichCookingLogCard/index.ts similarity index 100% rename from src/lib/cards/social/KichCookingLogCard/index.ts rename to apps/web/src/lib/cards/social/KichCookingLogCard/index.ts diff --git a/src/lib/cards/social/KichRecipeCard/CreateKichRecipeCardModal.svelte b/apps/web/src/lib/cards/social/KichRecipeCard/CreateKichRecipeCardModal.svelte similarity index 100% rename from src/lib/cards/social/KichRecipeCard/CreateKichRecipeCardModal.svelte rename to apps/web/src/lib/cards/social/KichRecipeCard/CreateKichRecipeCardModal.svelte diff --git a/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte b/apps/web/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte similarity index 100% rename from src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte rename to apps/web/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte diff --git a/src/lib/cards/social/KichRecipeCard/index.ts b/apps/web/src/lib/cards/social/KichRecipeCard/index.ts similarity index 100% rename from src/lib/cards/social/KichRecipeCard/index.ts rename to apps/web/src/lib/cards/social/KichRecipeCard/index.ts diff --git a/src/lib/cards/social/KichRecipeCollectionCard/CreateKichRecipeCollectionCardModal.svelte b/apps/web/src/lib/cards/social/KichRecipeCollectionCard/CreateKichRecipeCollectionCardModal.svelte similarity index 100% rename from src/lib/cards/social/KichRecipeCollectionCard/CreateKichRecipeCollectionCardModal.svelte rename to apps/web/src/lib/cards/social/KichRecipeCollectionCard/CreateKichRecipeCollectionCardModal.svelte diff --git a/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte b/apps/web/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte similarity index 100% rename from src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte rename to apps/web/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte diff --git a/src/lib/cards/social/KichRecipeCollectionCard/index.ts b/apps/web/src/lib/cards/social/KichRecipeCollectionCard/index.ts similarity index 100% rename from src/lib/cards/social/KichRecipeCollectionCard/index.ts rename to apps/web/src/lib/cards/social/KichRecipeCollectionCard/index.ts diff --git a/src/lib/cards/social/KickstarterCard/CreateKickstarterCardModal.svelte b/apps/web/src/lib/cards/social/KickstarterCard/CreateKickstarterCardModal.svelte similarity index 100% rename from src/lib/cards/social/KickstarterCard/CreateKickstarterCardModal.svelte rename to apps/web/src/lib/cards/social/KickstarterCard/CreateKickstarterCardModal.svelte diff --git a/src/lib/cards/social/KickstarterCard/KickstarterCard.svelte b/apps/web/src/lib/cards/social/KickstarterCard/KickstarterCard.svelte similarity index 100% rename from src/lib/cards/social/KickstarterCard/KickstarterCard.svelte rename to apps/web/src/lib/cards/social/KickstarterCard/KickstarterCard.svelte diff --git a/src/lib/cards/social/KickstarterCard/index.ts b/apps/web/src/lib/cards/social/KickstarterCard/index.ts similarity index 100% rename from src/lib/cards/social/KickstarterCard/index.ts rename to apps/web/src/lib/cards/social/KickstarterCard/index.ts diff --git a/src/lib/cards/social/LatestBlueskyPostCard/LatestBlueskyPostCard.svelte b/apps/web/src/lib/cards/social/LatestBlueskyPostCard/LatestBlueskyPostCard.svelte similarity index 100% rename from src/lib/cards/social/LatestBlueskyPostCard/LatestBlueskyPostCard.svelte rename to apps/web/src/lib/cards/social/LatestBlueskyPostCard/LatestBlueskyPostCard.svelte diff --git a/src/lib/cards/social/LatestBlueskyPostCard/index.ts b/apps/web/src/lib/cards/social/LatestBlueskyPostCard/index.ts similarity index 100% rename from src/lib/cards/social/LatestBlueskyPostCard/index.ts rename to apps/web/src/lib/cards/social/LatestBlueskyPostCard/index.ts diff --git a/src/lib/cards/social/MarginCard/MarginCard.svelte b/apps/web/src/lib/cards/social/MarginCard/MarginCard.svelte similarity index 100% rename from src/lib/cards/social/MarginCard/MarginCard.svelte rename to apps/web/src/lib/cards/social/MarginCard/MarginCard.svelte diff --git a/src/lib/cards/social/MarginCard/MarginCardSettings.svelte b/apps/web/src/lib/cards/social/MarginCard/MarginCardSettings.svelte similarity index 100% rename from src/lib/cards/social/MarginCard/MarginCardSettings.svelte rename to apps/web/src/lib/cards/social/MarginCard/MarginCardSettings.svelte diff --git a/src/lib/cards/social/MarginCard/index.ts b/apps/web/src/lib/cards/social/MarginCard/index.ts similarity index 100% rename from src/lib/cards/social/MarginCard/index.ts rename to apps/web/src/lib/cards/social/MarginCard/index.ts diff --git a/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte b/apps/web/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte similarity index 100% rename from src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte rename to apps/web/src/lib/cards/social/NpmxLikesCard/NpmxLikesCard.svelte diff --git a/src/lib/cards/social/NpmxLikesCard/index.ts b/apps/web/src/lib/cards/social/NpmxLikesCard/index.ts similarity index 100% rename from src/lib/cards/social/NpmxLikesCard/index.ts rename to apps/web/src/lib/cards/social/NpmxLikesCard/index.ts diff --git a/src/lib/cards/social/NpmxLikesLeaderboardCard/NpmxLikesLeaderboardCard.svelte b/apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/NpmxLikesLeaderboardCard.svelte similarity index 100% rename from src/lib/cards/social/NpmxLikesLeaderboardCard/NpmxLikesLeaderboardCard.svelte rename to apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/NpmxLikesLeaderboardCard.svelte diff --git a/src/lib/cards/social/NpmxLikesLeaderboardCard/api.remote.ts b/apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/api.remote.ts similarity index 100% rename from src/lib/cards/social/NpmxLikesLeaderboardCard/api.remote.ts rename to apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/api.remote.ts diff --git a/src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts b/apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts similarity index 100% rename from src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts rename to apps/web/src/lib/cards/social/NpmxLikesLeaderboardCard/index.ts diff --git a/src/lib/cards/social/ProductHuntCard/CreateProductHuntCardModal.svelte b/apps/web/src/lib/cards/social/ProductHuntCard/CreateProductHuntCardModal.svelte similarity index 100% rename from src/lib/cards/social/ProductHuntCard/CreateProductHuntCardModal.svelte rename to apps/web/src/lib/cards/social/ProductHuntCard/CreateProductHuntCardModal.svelte diff --git a/src/lib/cards/social/ProductHuntCard/ProductHuntCard.svelte b/apps/web/src/lib/cards/social/ProductHuntCard/ProductHuntCard.svelte similarity index 100% rename from src/lib/cards/social/ProductHuntCard/ProductHuntCard.svelte rename to apps/web/src/lib/cards/social/ProductHuntCard/ProductHuntCard.svelte diff --git a/src/lib/cards/social/ProductHuntCard/index.ts b/apps/web/src/lib/cards/social/ProductHuntCard/index.ts similarity index 100% rename from src/lib/cards/social/ProductHuntCard/index.ts rename to apps/web/src/lib/cards/social/ProductHuntCard/index.ts diff --git a/src/lib/cards/social/RPGActorCard/RPGActorCard.svelte b/apps/web/src/lib/cards/social/RPGActorCard/RPGActorCard.svelte similarity index 100% rename from src/lib/cards/social/RPGActorCard/RPGActorCard.svelte rename to apps/web/src/lib/cards/social/RPGActorCard/RPGActorCard.svelte diff --git a/src/lib/cards/social/RPGActorCard/index.ts b/apps/web/src/lib/cards/social/RPGActorCard/index.ts similarity index 100% rename from src/lib/cards/social/RPGActorCard/index.ts rename to apps/web/src/lib/cards/social/RPGActorCard/index.ts diff --git a/src/lib/cards/social/RoomyChatCard/CreateRoomyChatCardModal.svelte b/apps/web/src/lib/cards/social/RoomyChatCard/CreateRoomyChatCardModal.svelte similarity index 100% rename from src/lib/cards/social/RoomyChatCard/CreateRoomyChatCardModal.svelte rename to apps/web/src/lib/cards/social/RoomyChatCard/CreateRoomyChatCardModal.svelte diff --git a/src/lib/cards/social/RoomyChatCard/RoomyChatCard.svelte b/apps/web/src/lib/cards/social/RoomyChatCard/RoomyChatCard.svelte similarity index 100% rename from src/lib/cards/social/RoomyChatCard/RoomyChatCard.svelte rename to apps/web/src/lib/cards/social/RoomyChatCard/RoomyChatCard.svelte diff --git a/src/lib/cards/social/RoomyChatCard/api.remote.ts b/apps/web/src/lib/cards/social/RoomyChatCard/api.remote.ts similarity index 100% rename from src/lib/cards/social/RoomyChatCard/api.remote.ts rename to apps/web/src/lib/cards/social/RoomyChatCard/api.remote.ts diff --git a/src/lib/cards/social/RoomyChatCard/index.ts b/apps/web/src/lib/cards/social/RoomyChatCard/index.ts similarity index 100% rename from src/lib/cards/social/RoomyChatCard/index.ts rename to apps/web/src/lib/cards/social/RoomyChatCard/index.ts diff --git a/src/lib/cards/social/RoomyChatCard/types.ts b/apps/web/src/lib/cards/social/RoomyChatCard/types.ts similarity index 100% rename from src/lib/cards/social/RoomyChatCard/types.ts rename to apps/web/src/lib/cards/social/RoomyChatCard/types.ts diff --git a/src/lib/cards/social/SembleCollectionCard/CreateSembleCollectionCardModal.svelte b/apps/web/src/lib/cards/social/SembleCollectionCard/CreateSembleCollectionCardModal.svelte similarity index 100% rename from src/lib/cards/social/SembleCollectionCard/CreateSembleCollectionCardModal.svelte rename to apps/web/src/lib/cards/social/SembleCollectionCard/CreateSembleCollectionCardModal.svelte diff --git a/src/lib/cards/social/SembleCollectionCard/SembleCollectionCard.svelte b/apps/web/src/lib/cards/social/SembleCollectionCard/SembleCollectionCard.svelte similarity index 100% rename from src/lib/cards/social/SembleCollectionCard/SembleCollectionCard.svelte rename to apps/web/src/lib/cards/social/SembleCollectionCard/SembleCollectionCard.svelte diff --git a/src/lib/cards/social/SembleCollectionCard/index.ts b/apps/web/src/lib/cards/social/SembleCollectionCard/index.ts similarity index 100% rename from src/lib/cards/social/SembleCollectionCard/index.ts rename to apps/web/src/lib/cards/social/SembleCollectionCard/index.ts diff --git a/src/lib/cards/social/SkyboardCard/CreateSkyboardCardModal.svelte b/apps/web/src/lib/cards/social/SkyboardCard/CreateSkyboardCardModal.svelte similarity index 100% rename from src/lib/cards/social/SkyboardCard/CreateSkyboardCardModal.svelte rename to apps/web/src/lib/cards/social/SkyboardCard/CreateSkyboardCardModal.svelte diff --git a/src/lib/cards/social/SkyboardCard/SkyboardCard.svelte b/apps/web/src/lib/cards/social/SkyboardCard/SkyboardCard.svelte similarity index 100% rename from src/lib/cards/social/SkyboardCard/SkyboardCard.svelte rename to apps/web/src/lib/cards/social/SkyboardCard/SkyboardCard.svelte diff --git a/src/lib/cards/social/SkyboardCard/api.remote.ts b/apps/web/src/lib/cards/social/SkyboardCard/api.remote.ts similarity index 100% rename from src/lib/cards/social/SkyboardCard/api.remote.ts rename to apps/web/src/lib/cards/social/SkyboardCard/api.remote.ts diff --git a/src/lib/cards/social/SkyboardCard/index.ts b/apps/web/src/lib/cards/social/SkyboardCard/index.ts similarity index 100% rename from src/lib/cards/social/SkyboardCard/index.ts rename to apps/web/src/lib/cards/social/SkyboardCard/index.ts diff --git a/src/lib/cards/social/SkyboardCard/types.ts b/apps/web/src/lib/cards/social/SkyboardCard/types.ts similarity index 100% rename from src/lib/cards/social/SkyboardCard/types.ts rename to apps/web/src/lib/cards/social/SkyboardCard/types.ts diff --git a/src/lib/cards/social/UpcomingEventsCard/UpcomingEventsCard.svelte b/apps/web/src/lib/cards/social/UpcomingEventsCard/UpcomingEventsCard.svelte similarity index 100% rename from src/lib/cards/social/UpcomingEventsCard/UpcomingEventsCard.svelte rename to apps/web/src/lib/cards/social/UpcomingEventsCard/UpcomingEventsCard.svelte diff --git a/src/lib/cards/social/UpcomingEventsCard/index.ts b/apps/web/src/lib/cards/social/UpcomingEventsCard/index.ts similarity index 100% rename from src/lib/cards/social/UpcomingEventsCard/index.ts rename to apps/web/src/lib/cards/social/UpcomingEventsCard/index.ts diff --git a/src/lib/cards/social/UpcomingRsvpsCard/UpcomingRsvpsCard.svelte b/apps/web/src/lib/cards/social/UpcomingRsvpsCard/UpcomingRsvpsCard.svelte similarity index 100% rename from src/lib/cards/social/UpcomingRsvpsCard/UpcomingRsvpsCard.svelte rename to apps/web/src/lib/cards/social/UpcomingRsvpsCard/UpcomingRsvpsCard.svelte diff --git a/src/lib/cards/social/UpcomingRsvpsCard/index.ts b/apps/web/src/lib/cards/social/UpcomingRsvpsCard/index.ts similarity index 100% rename from src/lib/cards/social/UpcomingRsvpsCard/index.ts rename to apps/web/src/lib/cards/social/UpcomingRsvpsCard/index.ts diff --git a/src/lib/cards/social/VCardCard/VCardCard.svelte b/apps/web/src/lib/cards/social/VCardCard/VCardCard.svelte similarity index 100% rename from src/lib/cards/social/VCardCard/VCardCard.svelte rename to apps/web/src/lib/cards/social/VCardCard/VCardCard.svelte diff --git a/src/lib/cards/social/VCardCard/VCardCardSettings.svelte b/apps/web/src/lib/cards/social/VCardCard/VCardCardSettings.svelte similarity index 100% rename from src/lib/cards/social/VCardCard/VCardCardSettings.svelte rename to apps/web/src/lib/cards/social/VCardCard/VCardCardSettings.svelte diff --git a/src/lib/cards/social/VCardCard/index.ts b/apps/web/src/lib/cards/social/VCardCard/index.ts similarity index 100% rename from src/lib/cards/social/VCardCard/index.ts rename to apps/web/src/lib/cards/social/VCardCard/index.ts diff --git a/src/lib/cards/special/UpdatedBlentos/UpdatedBlentosCard.svelte b/apps/web/src/lib/cards/special/UpdatedBlentos/UpdatedBlentosCard.svelte similarity index 100% rename from src/lib/cards/special/UpdatedBlentos/UpdatedBlentosCard.svelte rename to apps/web/src/lib/cards/special/UpdatedBlentos/UpdatedBlentosCard.svelte diff --git a/src/lib/cards/special/UpdatedBlentos/index.ts b/apps/web/src/lib/cards/special/UpdatedBlentos/index.ts similarity index 100% rename from src/lib/cards/special/UpdatedBlentos/index.ts rename to apps/web/src/lib/cards/special/UpdatedBlentos/index.ts diff --git a/src/lib/cards/types.ts b/apps/web/src/lib/cards/types.ts similarity index 100% rename from src/lib/cards/types.ts rename to apps/web/src/lib/cards/types.ts diff --git a/src/lib/cards/utilities/ButtonCard/ButtonCard.svelte b/apps/web/src/lib/cards/utilities/ButtonCard/ButtonCard.svelte similarity index 100% rename from src/lib/cards/utilities/ButtonCard/ButtonCard.svelte rename to apps/web/src/lib/cards/utilities/ButtonCard/ButtonCard.svelte diff --git a/src/lib/cards/utilities/ButtonCard/ButtonCardSettings.svelte b/apps/web/src/lib/cards/utilities/ButtonCard/ButtonCardSettings.svelte similarity index 100% rename from src/lib/cards/utilities/ButtonCard/ButtonCardSettings.svelte rename to apps/web/src/lib/cards/utilities/ButtonCard/ButtonCardSettings.svelte diff --git a/src/lib/cards/utilities/ButtonCard/EditingButtonCard.svelte b/apps/web/src/lib/cards/utilities/ButtonCard/EditingButtonCard.svelte similarity index 100% rename from src/lib/cards/utilities/ButtonCard/EditingButtonCard.svelte rename to apps/web/src/lib/cards/utilities/ButtonCard/EditingButtonCard.svelte diff --git a/src/lib/cards/utilities/ButtonCard/index.ts b/apps/web/src/lib/cards/utilities/ButtonCard/index.ts similarity index 100% rename from src/lib/cards/utilities/ButtonCard/index.ts rename to apps/web/src/lib/cards/utilities/ButtonCard/index.ts diff --git a/src/lib/cards/utilities/ClockCard/ClockCard.svelte b/apps/web/src/lib/cards/utilities/ClockCard/ClockCard.svelte similarity index 100% rename from src/lib/cards/utilities/ClockCard/ClockCard.svelte rename to apps/web/src/lib/cards/utilities/ClockCard/ClockCard.svelte diff --git a/src/lib/cards/utilities/ClockCard/ClockCardSettings.svelte b/apps/web/src/lib/cards/utilities/ClockCard/ClockCardSettings.svelte similarity index 100% rename from src/lib/cards/utilities/ClockCard/ClockCardSettings.svelte rename to apps/web/src/lib/cards/utilities/ClockCard/ClockCardSettings.svelte diff --git a/src/lib/cards/utilities/ClockCard/index.ts b/apps/web/src/lib/cards/utilities/ClockCard/index.ts similarity index 100% rename from src/lib/cards/utilities/ClockCard/index.ts rename to apps/web/src/lib/cards/utilities/ClockCard/index.ts diff --git a/src/lib/cards/utilities/CountdownCard/CountdownCard.svelte b/apps/web/src/lib/cards/utilities/CountdownCard/CountdownCard.svelte similarity index 100% rename from src/lib/cards/utilities/CountdownCard/CountdownCard.svelte rename to apps/web/src/lib/cards/utilities/CountdownCard/CountdownCard.svelte diff --git a/src/lib/cards/utilities/CountdownCard/CountdownCardSettings.svelte b/apps/web/src/lib/cards/utilities/CountdownCard/CountdownCardSettings.svelte similarity index 100% rename from src/lib/cards/utilities/CountdownCard/CountdownCardSettings.svelte rename to apps/web/src/lib/cards/utilities/CountdownCard/CountdownCardSettings.svelte diff --git a/src/lib/cards/utilities/CountdownCard/index.ts b/apps/web/src/lib/cards/utilities/CountdownCard/index.ts similarity index 100% rename from src/lib/cards/utilities/CountdownCard/index.ts rename to apps/web/src/lib/cards/utilities/CountdownCard/index.ts diff --git a/src/lib/cards/utilities/TimerCard/TimerCard.svelte b/apps/web/src/lib/cards/utilities/TimerCard/TimerCard.svelte similarity index 100% rename from src/lib/cards/utilities/TimerCard/TimerCard.svelte rename to apps/web/src/lib/cards/utilities/TimerCard/TimerCard.svelte diff --git a/src/lib/cards/utilities/TimerCard/TimerCardSettings.svelte b/apps/web/src/lib/cards/utilities/TimerCard/TimerCardSettings.svelte similarity index 100% rename from src/lib/cards/utilities/TimerCard/TimerCardSettings.svelte rename to apps/web/src/lib/cards/utilities/TimerCard/TimerCardSettings.svelte diff --git a/src/lib/cards/utilities/TimerCard/index.ts b/apps/web/src/lib/cards/utilities/TimerCard/index.ts similarity index 100% rename from src/lib/cards/utilities/TimerCard/index.ts rename to apps/web/src/lib/cards/utilities/TimerCard/index.ts diff --git a/src/lib/cards/visual/DrawCard/DrawCard.svelte b/apps/web/src/lib/cards/visual/DrawCard/DrawCard.svelte similarity index 100% rename from src/lib/cards/visual/DrawCard/DrawCard.svelte rename to apps/web/src/lib/cards/visual/DrawCard/DrawCard.svelte diff --git a/src/lib/cards/visual/DrawCard/EditingDrawCard.svelte b/apps/web/src/lib/cards/visual/DrawCard/EditingDrawCard.svelte similarity index 100% rename from src/lib/cards/visual/DrawCard/EditingDrawCard.svelte rename to apps/web/src/lib/cards/visual/DrawCard/EditingDrawCard.svelte diff --git a/src/lib/cards/visual/DrawCard/index.ts b/apps/web/src/lib/cards/visual/DrawCard/index.ts similarity index 100% rename from src/lib/cards/visual/DrawCard/index.ts rename to apps/web/src/lib/cards/visual/DrawCard/index.ts diff --git a/src/lib/cards/visual/FluidTextCard/CreateFluidTextCardModal.svelte b/apps/web/src/lib/cards/visual/FluidTextCard/CreateFluidTextCardModal.svelte similarity index 100% rename from src/lib/cards/visual/FluidTextCard/CreateFluidTextCardModal.svelte rename to apps/web/src/lib/cards/visual/FluidTextCard/CreateFluidTextCardModal.svelte diff --git a/src/lib/cards/visual/FluidTextCard/EditingFluidTextCard.svelte b/apps/web/src/lib/cards/visual/FluidTextCard/EditingFluidTextCard.svelte similarity index 100% rename from src/lib/cards/visual/FluidTextCard/EditingFluidTextCard.svelte rename to apps/web/src/lib/cards/visual/FluidTextCard/EditingFluidTextCard.svelte diff --git a/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte b/apps/web/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte similarity index 100% rename from src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte rename to apps/web/src/lib/cards/visual/FluidTextCard/FluidTextCard.svelte diff --git a/src/lib/cards/visual/FluidTextCard/FluidTextCardSettings.svelte b/apps/web/src/lib/cards/visual/FluidTextCard/FluidTextCardSettings.svelte similarity index 100% rename from src/lib/cards/visual/FluidTextCard/FluidTextCardSettings.svelte rename to apps/web/src/lib/cards/visual/FluidTextCard/FluidTextCardSettings.svelte diff --git a/src/lib/cards/visual/FluidTextCard/index.ts b/apps/web/src/lib/cards/visual/FluidTextCard/index.ts similarity index 100% rename from src/lib/cards/visual/FluidTextCard/index.ts rename to apps/web/src/lib/cards/visual/FluidTextCard/index.ts diff --git a/src/lib/cards/visual/Model3DCard/CreateModel3DCardModal.svelte b/apps/web/src/lib/cards/visual/Model3DCard/CreateModel3DCardModal.svelte similarity index 100% rename from src/lib/cards/visual/Model3DCard/CreateModel3DCardModal.svelte rename to apps/web/src/lib/cards/visual/Model3DCard/CreateModel3DCardModal.svelte diff --git a/src/lib/cards/visual/Model3DCard/Model3DCard.svelte b/apps/web/src/lib/cards/visual/Model3DCard/Model3DCard.svelte similarity index 100% rename from src/lib/cards/visual/Model3DCard/Model3DCard.svelte rename to apps/web/src/lib/cards/visual/Model3DCard/Model3DCard.svelte diff --git a/src/lib/cards/visual/Model3DCard/Model3DScene.svelte b/apps/web/src/lib/cards/visual/Model3DCard/Model3DScene.svelte similarity index 100% rename from src/lib/cards/visual/Model3DCard/Model3DScene.svelte rename to apps/web/src/lib/cards/visual/Model3DCard/Model3DScene.svelte diff --git a/src/lib/cards/visual/Model3DCard/index.ts b/apps/web/src/lib/cards/visual/Model3DCard/index.ts similarity index 100% rename from src/lib/cards/visual/Model3DCard/index.ts rename to apps/web/src/lib/cards/visual/Model3DCard/index.ts diff --git a/src/lib/components/ImageGrid.svelte b/apps/web/src/lib/components/ImageGrid.svelte similarity index 100% rename from src/lib/components/ImageGrid.svelte rename to apps/web/src/lib/components/ImageGrid.svelte diff --git a/src/lib/components/MarkdownTextEditor.svelte b/apps/web/src/lib/components/MarkdownTextEditor.svelte similarity index 100% rename from src/lib/components/MarkdownTextEditor.svelte rename to apps/web/src/lib/components/MarkdownTextEditor.svelte diff --git a/src/lib/components/PlainTextEditor.svelte b/apps/web/src/lib/components/PlainTextEditor.svelte similarity index 100% rename from src/lib/components/PlainTextEditor.svelte rename to apps/web/src/lib/components/PlainTextEditor.svelte diff --git a/src/lib/components/Pronouns.svelte b/apps/web/src/lib/components/Pronouns.svelte similarity index 100% rename from src/lib/components/Pronouns.svelte rename to apps/web/src/lib/components/Pronouns.svelte diff --git a/src/lib/components/YoutubeVideoPlayer.svelte b/apps/web/src/lib/components/YoutubeVideoPlayer.svelte similarity index 100% rename from src/lib/components/YoutubeVideoPlayer.svelte rename to apps/web/src/lib/components/YoutubeVideoPlayer.svelte diff --git a/src/lib/components/bluesky-post/BlueskyPost.svelte b/apps/web/src/lib/components/bluesky-post/BlueskyPost.svelte similarity index 100% rename from src/lib/components/bluesky-post/BlueskyPost.svelte rename to apps/web/src/lib/components/bluesky-post/BlueskyPost.svelte diff --git a/src/lib/components/bluesky-post/index.ts b/apps/web/src/lib/components/bluesky-post/index.ts similarity index 100% rename from src/lib/components/bluesky-post/index.ts rename to apps/web/src/lib/components/bluesky-post/index.ts diff --git a/src/lib/components/card-command/CardCommand.svelte b/apps/web/src/lib/components/card-command/CardCommand.svelte similarity index 100% rename from src/lib/components/card-command/CardCommand.svelte rename to apps/web/src/lib/components/card-command/CardCommand.svelte diff --git a/src/lib/components/extensions/RichTextLink.ts b/apps/web/src/lib/components/extensions/RichTextLink.ts similarity index 100% rename from src/lib/components/extensions/RichTextLink.ts rename to apps/web/src/lib/components/extensions/RichTextLink.ts diff --git a/src/lib/components/image-store.ts b/apps/web/src/lib/components/image-store.ts similarity index 100% rename from src/lib/components/image-store.ts rename to apps/web/src/lib/components/image-store.ts diff --git a/src/lib/components/image-viewer/ImageViewerModal.svelte b/apps/web/src/lib/components/image-viewer/ImageViewerModal.svelte similarity index 100% rename from src/lib/components/image-viewer/ImageViewerModal.svelte rename to apps/web/src/lib/components/image-viewer/ImageViewerModal.svelte diff --git a/src/lib/components/image-viewer/ImageViewerProvider.svelte b/apps/web/src/lib/components/image-viewer/ImageViewerProvider.svelte similarity index 100% rename from src/lib/components/image-viewer/ImageViewerProvider.svelte rename to apps/web/src/lib/components/image-viewer/ImageViewerProvider.svelte diff --git a/src/lib/components/image-viewer/imageViewer.svelte.ts b/apps/web/src/lib/components/image-viewer/imageViewer.svelte.ts similarity index 100% rename from src/lib/components/image-viewer/imageViewer.svelte.ts rename to apps/web/src/lib/components/image-viewer/imageViewer.svelte.ts diff --git a/src/lib/components/index.ts b/apps/web/src/lib/components/index.ts similarity index 100% rename from src/lib/components/index.ts rename to apps/web/src/lib/components/index.ts diff --git a/src/lib/components/modal/Modal.svelte b/apps/web/src/lib/components/modal/Modal.svelte similarity index 100% rename from src/lib/components/modal/Modal.svelte rename to apps/web/src/lib/components/modal/Modal.svelte diff --git a/src/lib/components/modals/MobileWarningModal.svelte b/apps/web/src/lib/components/modals/MobileWarningModal.svelte similarity index 100% rename from src/lib/components/modals/MobileWarningModal.svelte rename to apps/web/src/lib/components/modals/MobileWarningModal.svelte diff --git a/src/lib/components/post/Post.svelte b/apps/web/src/lib/components/post/Post.svelte similarity index 100% rename from src/lib/components/post/Post.svelte rename to apps/web/src/lib/components/post/Post.svelte diff --git a/src/lib/components/post/PostAction.svelte b/apps/web/src/lib/components/post/PostAction.svelte similarity index 100% rename from src/lib/components/post/PostAction.svelte rename to apps/web/src/lib/components/post/PostAction.svelte diff --git a/src/lib/components/post/PostEmbed.svelte b/apps/web/src/lib/components/post/PostEmbed.svelte similarity index 100% rename from src/lib/components/post/PostEmbed.svelte rename to apps/web/src/lib/components/post/PostEmbed.svelte diff --git a/src/lib/components/post/embeds/Embed.svelte b/apps/web/src/lib/components/post/embeds/Embed.svelte similarity index 100% rename from src/lib/components/post/embeds/Embed.svelte rename to apps/web/src/lib/components/post/embeds/Embed.svelte diff --git a/src/lib/components/post/embeds/External.svelte b/apps/web/src/lib/components/post/embeds/External.svelte similarity index 100% rename from src/lib/components/post/embeds/External.svelte rename to apps/web/src/lib/components/post/embeds/External.svelte diff --git a/src/lib/components/post/embeds/Images.svelte b/apps/web/src/lib/components/post/embeds/Images.svelte similarity index 100% rename from src/lib/components/post/embeds/Images.svelte rename to apps/web/src/lib/components/post/embeds/Images.svelte diff --git a/src/lib/components/post/embeds/QuotedPost.svelte b/apps/web/src/lib/components/post/embeds/QuotedPost.svelte similarity index 100% rename from src/lib/components/post/embeds/QuotedPost.svelte rename to apps/web/src/lib/components/post/embeds/QuotedPost.svelte diff --git a/src/lib/components/post/embeds/Video.svelte b/apps/web/src/lib/components/post/embeds/Video.svelte similarity index 100% rename from src/lib/components/post/embeds/Video.svelte rename to apps/web/src/lib/components/post/embeds/Video.svelte diff --git a/src/lib/components/post/index.ts b/apps/web/src/lib/components/post/index.ts similarity index 100% rename from src/lib/components/post/index.ts rename to apps/web/src/lib/components/post/index.ts diff --git a/src/lib/components/qr/QRCodeDisplay.svelte b/apps/web/src/lib/components/qr/QRCodeDisplay.svelte similarity index 100% rename from src/lib/components/qr/QRCodeDisplay.svelte rename to apps/web/src/lib/components/qr/QRCodeDisplay.svelte diff --git a/src/lib/components/qr/QRCodeModal.svelte b/apps/web/src/lib/components/qr/QRCodeModal.svelte similarity index 100% rename from src/lib/components/qr/QRCodeModal.svelte rename to apps/web/src/lib/components/qr/QRCodeModal.svelte diff --git a/src/lib/components/qr/QRModalProvider.svelte b/apps/web/src/lib/components/qr/QRModalProvider.svelte similarity index 100% rename from src/lib/components/qr/QRModalProvider.svelte rename to apps/web/src/lib/components/qr/QRModalProvider.svelte diff --git a/src/lib/components/qr/qrOverlay.svelte.ts b/apps/web/src/lib/components/qr/qrOverlay.svelte.ts similarity index 100% rename from src/lib/components/qr/qrOverlay.svelte.ts rename to apps/web/src/lib/components/qr/qrOverlay.svelte.ts diff --git a/src/lib/components/rich-text-editor/Icon.svelte b/apps/web/src/lib/components/rich-text-editor/Icon.svelte similarity index 100% rename from src/lib/components/rich-text-editor/Icon.svelte rename to apps/web/src/lib/components/rich-text-editor/Icon.svelte diff --git a/src/lib/components/rich-text-editor/RichTextEditor.svelte b/apps/web/src/lib/components/rich-text-editor/RichTextEditor.svelte similarity index 100% rename from src/lib/components/rich-text-editor/RichTextEditor.svelte rename to apps/web/src/lib/components/rich-text-editor/RichTextEditor.svelte diff --git a/src/lib/components/rich-text-editor/RichTextEditorLinkMenu.svelte b/apps/web/src/lib/components/rich-text-editor/RichTextEditorLinkMenu.svelte similarity index 100% rename from src/lib/components/rich-text-editor/RichTextEditorLinkMenu.svelte rename to apps/web/src/lib/components/rich-text-editor/RichTextEditorLinkMenu.svelte diff --git a/src/lib/components/rich-text-editor/RichTextEditorMenu.svelte b/apps/web/src/lib/components/rich-text-editor/RichTextEditorMenu.svelte similarity index 100% rename from src/lib/components/rich-text-editor/RichTextEditorMenu.svelte rename to apps/web/src/lib/components/rich-text-editor/RichTextEditorMenu.svelte diff --git a/src/lib/components/rich-text-editor/RichTextLink.ts b/apps/web/src/lib/components/rich-text-editor/RichTextLink.ts similarity index 100% rename from src/lib/components/rich-text-editor/RichTextLink.ts rename to apps/web/src/lib/components/rich-text-editor/RichTextLink.ts diff --git a/src/lib/components/rich-text-editor/Select.svelte b/apps/web/src/lib/components/rich-text-editor/Select.svelte similarity index 100% rename from src/lib/components/rich-text-editor/Select.svelte rename to apps/web/src/lib/components/rich-text-editor/Select.svelte diff --git a/src/lib/components/rich-text-editor/code.css b/apps/web/src/lib/components/rich-text-editor/code.css similarity index 100% rename from src/lib/components/rich-text-editor/code.css rename to apps/web/src/lib/components/rich-text-editor/code.css diff --git a/src/lib/components/rich-text-editor/image-upload/ImageUploadComponent.svelte b/apps/web/src/lib/components/rich-text-editor/image-upload/ImageUploadComponent.svelte similarity index 100% rename from src/lib/components/rich-text-editor/image-upload/ImageUploadComponent.svelte rename to apps/web/src/lib/components/rich-text-editor/image-upload/ImageUploadComponent.svelte diff --git a/src/lib/components/rich-text-editor/image-upload/ImageUploadNode.ts b/apps/web/src/lib/components/rich-text-editor/image-upload/ImageUploadNode.ts similarity index 100% rename from src/lib/components/rich-text-editor/image-upload/ImageUploadNode.ts rename to apps/web/src/lib/components/rich-text-editor/image-upload/ImageUploadNode.ts diff --git a/src/lib/components/rich-text-editor/index.ts b/apps/web/src/lib/components/rich-text-editor/index.ts similarity index 100% rename from src/lib/components/rich-text-editor/index.ts rename to apps/web/src/lib/components/rich-text-editor/index.ts diff --git a/src/lib/components/rich-text-editor/slash-menu/SuggestionSelect.svelte b/apps/web/src/lib/components/rich-text-editor/slash-menu/SuggestionSelect.svelte similarity index 100% rename from src/lib/components/rich-text-editor/slash-menu/SuggestionSelect.svelte rename to apps/web/src/lib/components/rich-text-editor/slash-menu/SuggestionSelect.svelte diff --git a/src/lib/components/rich-text-editor/slash-menu/index.ts b/apps/web/src/lib/components/rich-text-editor/slash-menu/index.ts similarity index 100% rename from src/lib/components/rich-text-editor/slash-menu/index.ts rename to apps/web/src/lib/components/rich-text-editor/slash-menu/index.ts diff --git a/src/lib/components/select-theme/SelectTheme.svelte b/apps/web/src/lib/components/select-theme/SelectTheme.svelte similarity index 100% rename from src/lib/components/select-theme/SelectTheme.svelte rename to apps/web/src/lib/components/select-theme/SelectTheme.svelte diff --git a/src/lib/components/select-theme/SelectThemePopover.svelte b/apps/web/src/lib/components/select-theme/SelectThemePopover.svelte similarity index 100% rename from src/lib/components/select-theme/SelectThemePopover.svelte rename to apps/web/src/lib/components/select-theme/SelectThemePopover.svelte diff --git a/src/lib/components/select-theme/index.ts b/apps/web/src/lib/components/select-theme/index.ts similarity index 100% rename from src/lib/components/select-theme/index.ts rename to apps/web/src/lib/components/select-theme/index.ts diff --git a/src/lib/contrail.config.ts b/apps/web/src/lib/contrail.config.ts similarity index 85% rename from src/lib/contrail.config.ts rename to apps/web/src/lib/contrail.config.ts index c7dc246f..cb6a245b 100644 --- a/src/lib/contrail.config.ts +++ b/apps/web/src/lib/contrail.config.ts @@ -3,6 +3,13 @@ import type { ContrailConfig } from '@atmo-dev/contrail'; export const config: ContrailConfig = { namespace: 'app.blento', collections: { + node: { + collection: 'app.blento.node', + queryable: { + page: {}, + type: {} + } + }, card: { collection: 'app.blento.card', queryable: { diff --git a/src/lib/contrail/client.ts b/apps/web/src/lib/contrail/client.ts similarity index 100% rename from src/lib/contrail/client.ts rename to apps/web/src/lib/contrail/client.ts diff --git a/src/lib/contrail/index.ts b/apps/web/src/lib/contrail/index.ts similarity index 100% rename from src/lib/contrail/index.ts rename to apps/web/src/lib/contrail/index.ts diff --git a/src/lib/embed/AtmoEmbed.svelte b/apps/web/src/lib/embed/AtmoEmbed.svelte similarity index 100% rename from src/lib/embed/AtmoEmbed.svelte rename to apps/web/src/lib/embed/AtmoEmbed.svelte diff --git a/src/lib/embed/allowlist.ts b/apps/web/src/lib/embed/allowlist.ts similarity index 100% rename from src/lib/embed/allowlist.ts rename to apps/web/src/lib/embed/allowlist.ts diff --git a/src/lib/embed/embed.remote.ts b/apps/web/src/lib/embed/embed.remote.ts similarity index 100% rename from src/lib/embed/embed.remote.ts rename to apps/web/src/lib/embed/embed.remote.ts diff --git a/src/lib/events/adapter.ts b/apps/web/src/lib/events/adapter.ts similarity index 100% rename from src/lib/events/adapter.ts rename to apps/web/src/lib/events/adapter.ts diff --git a/src/lib/events/atmo-appview.ts b/apps/web/src/lib/events/atmo-appview.ts similarity index 100% rename from src/lib/events/atmo-appview.ts rename to apps/web/src/lib/events/atmo-appview.ts diff --git a/src/lib/events/fetch-attendees.ts b/apps/web/src/lib/events/fetch-attendees.ts similarity index 100% rename from src/lib/events/fetch-attendees.ts rename to apps/web/src/lib/events/fetch-attendees.ts diff --git a/src/lib/helpers/actor.ts b/apps/web/src/lib/helpers/actor.ts similarity index 100% rename from src/lib/helpers/actor.ts rename to apps/web/src/lib/helpers/actor.ts diff --git a/src/lib/helpers/analytics.ts b/apps/web/src/lib/helpers/analytics.ts similarity index 100% rename from src/lib/helpers/analytics.ts rename to apps/web/src/lib/helpers/analytics.ts diff --git a/src/lib/helpers/cache.ts b/apps/web/src/lib/helpers/cache.ts similarity index 100% rename from src/lib/helpers/cache.ts rename to apps/web/src/lib/helpers/cache.ts diff --git a/src/lib/helpers/dns.ts b/apps/web/src/lib/helpers/dns.ts similarity index 100% rename from src/lib/helpers/dns.ts rename to apps/web/src/lib/helpers/dns.ts diff --git a/src/lib/helpers/images.ts b/apps/web/src/lib/helpers/images.ts similarity index 100% rename from src/lib/helpers/images.ts rename to apps/web/src/lib/helpers/images.ts diff --git a/src/lib/helpers/items.ts b/apps/web/src/lib/helpers/items.ts similarity index 100% rename from src/lib/helpers/items.ts rename to apps/web/src/lib/helpers/items.ts diff --git a/src/lib/helpers/links.ts b/apps/web/src/lib/helpers/links.ts similarity index 100% rename from src/lib/helpers/links.ts rename to apps/web/src/lib/helpers/links.ts diff --git a/src/lib/helpers/sanitize.ts b/apps/web/src/lib/helpers/sanitize.ts similarity index 100% rename from src/lib/helpers/sanitize.ts rename to apps/web/src/lib/helpers/sanitize.ts diff --git a/apps/web/src/lib/helpers/save.ts b/apps/web/src/lib/helpers/save.ts new file mode 100644 index 00000000..43060c24 --- /dev/null +++ b/apps/web/src/lib/helpers/save.ts @@ -0,0 +1,183 @@ +import type { Item, WebsiteData, SectionRecord } from '../types'; +import { CardDefinitionsByType } from '../cards'; +import { deleteRecord, putRecord } from '$lib/atproto'; +import { getName, getDescription, getHideProfileSection } from './website'; +import { planPageWrite, type V1Card, type V1Section } from '@blento/schema'; +import { env } from '$env/dynamic/public'; + +/** + * Node migration (app.blento.node storage) is OFF by default. The prod read path (contrail) still + * serves card/section, so writing nodes + deleting card/section would blank a page there. Enable it + * per-deploy (dev / self-host) only once the read path resolves app.blento.node everywhere — set + * PUBLIC_ENABLE_NODE_MIGRATION=true. The read side is already dual-format, so flipping this is safe + * wherever node reads work, and a half-migrated repo still renders (migrate-on-read covers the rest). + */ +function nodeMigrationEnabled(): boolean { + return env.PUBLIC_ENABLE_NODE_MIGRATION === 'true'; +} + +export async function savePage( + data: WebsiteData, + currentItems: Item[], + originalCards: Item[], + originalPublication: string, + originalSections?: SectionRecord[] +) { + const promises = []; + const useNodes = nodeMigrationEnabled(); + + // Run each card type's upload side effect (e.g. image blobs) before snapshotting content. + const uploadedItems: Item[] = []; + for (let item of currentItems) { + item.updatedAt = new Date().toISOString(); + const cardDef = CardDefinitionsByType[item.cardType]; + if (cardDef?.upload) item = await cardDef.upload(item); + uploadedItems.push(item); + } + + if (useNodes) { + // Migrate-on-save: persist the page as app.blento.node records, retiring the legacy card/section + // records the first time the page is migrated. Geometry -> layout, color -> style, content -> data. + const plan = planPageWrite({ + sections: data.sections as unknown as V1Section[], + cards: uploadedItems as unknown as V1Card[], + page: data.page, + updatedAt: new Date().toISOString(), + storedNodeIds: data.migratedStorage ? (data.nodes ?? []).map((n) => n.id) : [], + legacyCardIds: data.migratedStorage ? [] : originalCards.map((c) => c.id), + legacySectionIds: data.migratedStorage ? [] : (originalSections ?? []).map((s) => s.id) + }); + + for (const { rkey, record } of plan.nodePuts) { + promises.push( + putRecord({ + collection: 'app.blento.node', + rkey, + record: record as unknown as Record + }) + ); + } + // Deletes are tolerant: a not-yet-existing legacy/removed record is a no-op, not an error. + for (const rkey of plan.nodeDeletes) { + promises.push(deleteRecord({ collection: 'app.blento.node', rkey }).catch(() => {})); + } + for (const rkey of plan.cardDeletes) { + promises.push(deleteRecord({ collection: 'app.blento.card', rkey }).catch(() => {})); + } + for (const rkey of plan.sectionDeletes) { + promises.push(deleteRecord({ collection: 'app.blento.section', rkey }).catch(() => {})); + } + } else { + // Legacy path (v1 behavior): write card + section records. + for (const section of data.sections) { + section.updatedAt = new Date().toISOString(); + section.version = 1; + const record = JSON.parse(JSON.stringify(section)); + const rkey = record.id; + delete record.id; + promises.push(putRecord({ collection: 'app.blento.section', rkey, record })); + } + if (originalSections) { + for (const original of originalSections) { + if (!data.sections.find((s) => s.id === original.id)) { + promises.push(deleteRecord({ collection: 'app.blento.section', rkey: original.id })); + } + } + } + for (const item of uploadedItems) { + const parsedItem = JSON.parse(JSON.stringify(item)); + parsedItem.page = data.page; + parsedItem.version = 2; + promises.push( + putRecord({ collection: 'app.blento.card', rkey: parsedItem.id, record: parsedItem }) + ); + } + for (const originalItem of originalCards) { + if (!currentItems.find((i) => i.id === originalItem.id)) { + promises.push(deleteRecord({ collection: 'app.blento.card', rkey: originalItem.id })); + } + } + } + + if ( + data.publication?.preferences?.hideProfile !== undefined && + data.publication?.preferences?.hideProfileSection === undefined + ) { + data.publication.preferences.hideProfileSection = data.publication?.preferences?.hideProfile; + } + + // With nodes on, the root record moves to app.blento.page for all pages and the legacy + // site.standard.publication store is retired on first migration. Legacy path keeps v1's split + // (app.blento.page for sub-pages, site.standard.publication for the main page). + const needsRootMigration = useNodes && data.page === 'blento.self' && !data.migratedStorage; + if ( + !originalPublication || + originalPublication !== JSON.stringify(data.publication) || + needsRootMigration + ) { + data.publication ??= { + name: getName(data), + description: getDescription(data), + preferences: { + hideProfileSection: getHideProfileSection(data) + } + }; + + if (!data.publication.url) { + data.publication.url = 'https://blento.app/' + data.handle; + + if (data.page !== 'blento.self') { + data.publication.url += '/' + data.page.replace('blento.', ''); + } + } + + if (useNodes || data.page !== 'blento.self') { + promises.push( + putRecord({ collection: 'app.blento.page', rkey: data.page, record: data.publication }) + ); + } else { + promises.push( + putRecord({ + collection: 'site.standard.publication', + rkey: data.page, + record: data.publication + }) + ); + } + if (needsRootMigration) { + promises.push( + deleteRecord({ collection: 'site.standard.publication', rkey: data.page }).catch(() => {}) + ); + } + + console.log('updating or adding publication', data.publication); + } + + // check if pronouns edited and save + if (data.pronounsRecord?.value?.sets?.length) { + const existing = data.pronounsRecord.value; + const now = new Date().toISOString(); + const record: Record = { + $type: 'app.nearhorizon.actor.pronouns', + sets: existing.sets, + displayMode: existing.displayMode ?? 'all', + createdAt: existing.createdAt ?? now + }; + if (existing.createdAt) { + record.updatedAt = now; + } + promises.push( + putRecord({ + collection: 'app.nearhorizon.actor.pronouns', + rkey: 'self', + record + }) + ); + } + + await Promise.all(promises); + + // Invalidate cached OG image so the next scrape regenerates it. + // Fire-and-forget; server checks auth against the signed-cookie session. + fetch(`/${data.did}/og-new.png`, { method: 'DELETE' }).catch(() => {}); +} diff --git a/src/lib/helpers/ssrf.ts b/apps/web/src/lib/helpers/ssrf.ts similarity index 100% rename from src/lib/helpers/ssrf.ts rename to apps/web/src/lib/helpers/ssrf.ts diff --git a/src/lib/helpers/utils.ts b/apps/web/src/lib/helpers/utils.ts similarity index 100% rename from src/lib/helpers/utils.ts rename to apps/web/src/lib/helpers/utils.ts diff --git a/src/lib/helpers/website.ts b/apps/web/src/lib/helpers/website.ts similarity index 100% rename from src/lib/helpers/website.ts rename to apps/web/src/lib/helpers/website.ts diff --git a/src/lib/index.ts b/apps/web/src/lib/index.ts similarity index 100% rename from src/lib/index.ts rename to apps/web/src/lib/index.ts diff --git a/src/lib/layout/EditableGrid.svelte b/apps/web/src/lib/layout/EditableGrid.svelte similarity index 100% rename from src/lib/layout/EditableGrid.svelte rename to apps/web/src/lib/layout/EditableGrid.svelte diff --git a/src/lib/layout/algorithms.ts b/apps/web/src/lib/layout/algorithms.ts similarity index 100% rename from src/lib/layout/algorithms.ts rename to apps/web/src/lib/layout/algorithms.ts diff --git a/src/lib/layout/grid.ts b/apps/web/src/lib/layout/grid.ts similarity index 100% rename from src/lib/layout/grid.ts rename to apps/web/src/lib/layout/grid.ts diff --git a/src/lib/layout/index.ts b/apps/web/src/lib/layout/index.ts similarity index 100% rename from src/lib/layout/index.ts rename to apps/web/src/lib/layout/index.ts diff --git a/src/lib/layout/mirror.test.ts b/apps/web/src/lib/layout/mirror.test.ts similarity index 100% rename from src/lib/layout/mirror.test.ts rename to apps/web/src/lib/layout/mirror.test.ts diff --git a/src/lib/layout/mirror.ts b/apps/web/src/lib/layout/mirror.ts similarity index 100% rename from src/lib/layout/mirror.ts rename to apps/web/src/lib/layout/mirror.ts diff --git a/src/lib/sections/ColumnsSection/ColumnsSection.svelte b/apps/web/src/lib/sections/ColumnsSection/ColumnsSection.svelte similarity index 100% rename from src/lib/sections/ColumnsSection/ColumnsSection.svelte rename to apps/web/src/lib/sections/ColumnsSection/ColumnsSection.svelte diff --git a/src/lib/sections/ColumnsSection/ColumnsSectionSettings.svelte b/apps/web/src/lib/sections/ColumnsSection/ColumnsSectionSettings.svelte similarity index 100% rename from src/lib/sections/ColumnsSection/ColumnsSectionSettings.svelte rename to apps/web/src/lib/sections/ColumnsSection/ColumnsSectionSettings.svelte diff --git a/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte b/apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte similarity index 100% rename from src/lib/sections/ColumnsSection/EditingColumnsSection.svelte rename to apps/web/src/lib/sections/ColumnsSection/EditingColumnsSection.svelte diff --git a/src/lib/sections/ColumnsSection/index.ts b/apps/web/src/lib/sections/ColumnsSection/index.ts similarity index 100% rename from src/lib/sections/ColumnsSection/index.ts rename to apps/web/src/lib/sections/ColumnsSection/index.ts diff --git a/src/lib/sections/GallerySection/EditingGallerySection.svelte b/apps/web/src/lib/sections/GallerySection/EditingGallerySection.svelte similarity index 100% rename from src/lib/sections/GallerySection/EditingGallerySection.svelte rename to apps/web/src/lib/sections/GallerySection/EditingGallerySection.svelte diff --git a/src/lib/sections/GallerySection/GallerySection.svelte b/apps/web/src/lib/sections/GallerySection/GallerySection.svelte similarity index 100% rename from src/lib/sections/GallerySection/GallerySection.svelte rename to apps/web/src/lib/sections/GallerySection/GallerySection.svelte diff --git a/src/lib/sections/GallerySection/GallerySectionSettings.svelte b/apps/web/src/lib/sections/GallerySection/GallerySectionSettings.svelte similarity index 100% rename from src/lib/sections/GallerySection/GallerySectionSettings.svelte rename to apps/web/src/lib/sections/GallerySection/GallerySectionSettings.svelte diff --git a/src/lib/sections/GallerySection/index.ts b/apps/web/src/lib/sections/GallerySection/index.ts similarity index 100% rename from src/lib/sections/GallerySection/index.ts rename to apps/web/src/lib/sections/GallerySection/index.ts diff --git a/src/lib/sections/GridSection/EditingGridSection.svelte b/apps/web/src/lib/sections/GridSection/EditingGridSection.svelte similarity index 100% rename from src/lib/sections/GridSection/EditingGridSection.svelte rename to apps/web/src/lib/sections/GridSection/EditingGridSection.svelte diff --git a/src/lib/sections/GridSection/GridSection.svelte b/apps/web/src/lib/sections/GridSection/GridSection.svelte similarity index 100% rename from src/lib/sections/GridSection/GridSection.svelte rename to apps/web/src/lib/sections/GridSection/GridSection.svelte diff --git a/src/lib/sections/GridSection/add-item.ts b/apps/web/src/lib/sections/GridSection/add-item.ts similarity index 100% rename from src/lib/sections/GridSection/add-item.ts rename to apps/web/src/lib/sections/GridSection/add-item.ts diff --git a/src/lib/sections/GridSection/index.ts b/apps/web/src/lib/sections/GridSection/index.ts similarity index 100% rename from src/lib/sections/GridSection/index.ts rename to apps/web/src/lib/sections/GridSection/index.ts diff --git a/src/lib/sections/GridSection/shared.ts b/apps/web/src/lib/sections/GridSection/shared.ts similarity index 100% rename from src/lib/sections/GridSection/shared.ts rename to apps/web/src/lib/sections/GridSection/shared.ts diff --git a/src/lib/sections/HeroSection/Decoration.svelte b/apps/web/src/lib/sections/HeroSection/Decoration.svelte similarity index 100% rename from src/lib/sections/HeroSection/Decoration.svelte rename to apps/web/src/lib/sections/HeroSection/Decoration.svelte diff --git a/src/lib/sections/HeroSection/EditingHeroSection.svelte b/apps/web/src/lib/sections/HeroSection/EditingHeroSection.svelte similarity index 100% rename from src/lib/sections/HeroSection/EditingHeroSection.svelte rename to apps/web/src/lib/sections/HeroSection/EditingHeroSection.svelte diff --git a/src/lib/sections/HeroSection/HeroSection.svelte b/apps/web/src/lib/sections/HeroSection/HeroSection.svelte similarity index 100% rename from src/lib/sections/HeroSection/HeroSection.svelte rename to apps/web/src/lib/sections/HeroSection/HeroSection.svelte diff --git a/src/lib/sections/HeroSection/HeroSectionSettings.svelte b/apps/web/src/lib/sections/HeroSection/HeroSectionSettings.svelte similarity index 100% rename from src/lib/sections/HeroSection/HeroSectionSettings.svelte rename to apps/web/src/lib/sections/HeroSection/HeroSectionSettings.svelte diff --git a/src/lib/sections/HeroSection/index.ts b/apps/web/src/lib/sections/HeroSection/index.ts similarity index 100% rename from src/lib/sections/HeroSection/index.ts rename to apps/web/src/lib/sections/HeroSection/index.ts diff --git a/src/lib/sections/HeroSection/shared.ts b/apps/web/src/lib/sections/HeroSection/shared.ts similarity index 100% rename from src/lib/sections/HeroSection/shared.ts rename to apps/web/src/lib/sections/HeroSection/shared.ts diff --git a/src/lib/sections/RowsSection/EditingRowsSection.svelte b/apps/web/src/lib/sections/RowsSection/EditingRowsSection.svelte similarity index 100% rename from src/lib/sections/RowsSection/EditingRowsSection.svelte rename to apps/web/src/lib/sections/RowsSection/EditingRowsSection.svelte diff --git a/src/lib/sections/RowsSection/RowsSection.svelte b/apps/web/src/lib/sections/RowsSection/RowsSection.svelte similarity index 100% rename from src/lib/sections/RowsSection/RowsSection.svelte rename to apps/web/src/lib/sections/RowsSection/RowsSection.svelte diff --git a/src/lib/sections/RowsSection/index.ts b/apps/web/src/lib/sections/RowsSection/index.ts similarity index 100% rename from src/lib/sections/RowsSection/index.ts rename to apps/web/src/lib/sections/RowsSection/index.ts diff --git a/src/lib/sections/RowsSection/shared.ts b/apps/web/src/lib/sections/RowsSection/shared.ts similarity index 100% rename from src/lib/sections/RowsSection/shared.ts rename to apps/web/src/lib/sections/RowsSection/shared.ts diff --git a/src/lib/sections/SectionChrome.svelte b/apps/web/src/lib/sections/SectionChrome.svelte similarity index 100% rename from src/lib/sections/SectionChrome.svelte rename to apps/web/src/lib/sections/SectionChrome.svelte diff --git a/src/lib/sections/SingleCardSection/EditingSingleCardSection.svelte b/apps/web/src/lib/sections/SingleCardSection/EditingSingleCardSection.svelte similarity index 100% rename from src/lib/sections/SingleCardSection/EditingSingleCardSection.svelte rename to apps/web/src/lib/sections/SingleCardSection/EditingSingleCardSection.svelte diff --git a/src/lib/sections/SingleCardSection/SingleCardSection.svelte b/apps/web/src/lib/sections/SingleCardSection/SingleCardSection.svelte similarity index 100% rename from src/lib/sections/SingleCardSection/SingleCardSection.svelte rename to apps/web/src/lib/sections/SingleCardSection/SingleCardSection.svelte diff --git a/src/lib/sections/SingleCardSection/SingleCardSectionSettings.svelte b/apps/web/src/lib/sections/SingleCardSection/SingleCardSectionSettings.svelte similarity index 100% rename from src/lib/sections/SingleCardSection/SingleCardSectionSettings.svelte rename to apps/web/src/lib/sections/SingleCardSection/SingleCardSectionSettings.svelte diff --git a/src/lib/sections/SingleCardSection/index.ts b/apps/web/src/lib/sections/SingleCardSection/index.ts similarity index 100% rename from src/lib/sections/SingleCardSection/index.ts rename to apps/web/src/lib/sections/SingleCardSection/index.ts diff --git a/src/lib/sections/SingleCardSection/shared.ts b/apps/web/src/lib/sections/SingleCardSection/shared.ts similarity index 100% rename from src/lib/sections/SingleCardSection/shared.ts rename to apps/web/src/lib/sections/SingleCardSection/shared.ts diff --git a/src/lib/sections/TextSection/EditingTextSection.svelte b/apps/web/src/lib/sections/TextSection/EditingTextSection.svelte similarity index 100% rename from src/lib/sections/TextSection/EditingTextSection.svelte rename to apps/web/src/lib/sections/TextSection/EditingTextSection.svelte diff --git a/src/lib/sections/TextSection/TextSection.svelte b/apps/web/src/lib/sections/TextSection/TextSection.svelte similarity index 100% rename from src/lib/sections/TextSection/TextSection.svelte rename to apps/web/src/lib/sections/TextSection/TextSection.svelte diff --git a/src/lib/sections/TextSection/TextSectionSettings.svelte b/apps/web/src/lib/sections/TextSection/TextSectionSettings.svelte similarity index 100% rename from src/lib/sections/TextSection/TextSectionSettings.svelte rename to apps/web/src/lib/sections/TextSection/TextSectionSettings.svelte diff --git a/src/lib/sections/TextSection/index.ts b/apps/web/src/lib/sections/TextSection/index.ts similarity index 100% rename from src/lib/sections/TextSection/index.ts rename to apps/web/src/lib/sections/TextSection/index.ts diff --git a/src/lib/sections/TextSection/shared.ts b/apps/web/src/lib/sections/TextSection/shared.ts similarity index 100% rename from src/lib/sections/TextSection/shared.ts rename to apps/web/src/lib/sections/TextSection/shared.ts diff --git a/src/lib/sections/feature-flag.ts b/apps/web/src/lib/sections/feature-flag.ts similarity index 100% rename from src/lib/sections/feature-flag.ts rename to apps/web/src/lib/sections/feature-flag.ts diff --git a/src/lib/sections/index.ts b/apps/web/src/lib/sections/index.ts similarity index 100% rename from src/lib/sections/index.ts rename to apps/web/src/lib/sections/index.ts diff --git a/src/lib/sections/migrate.ts b/apps/web/src/lib/sections/migrate.ts similarity index 100% rename from src/lib/sections/migrate.ts rename to apps/web/src/lib/sections/migrate.ts diff --git a/src/lib/sections/types.ts b/apps/web/src/lib/sections/types.ts similarity index 100% rename from src/lib/sections/types.ts rename to apps/web/src/lib/sections/types.ts diff --git a/src/lib/types.ts b/apps/web/src/lib/types.ts similarity index 82% rename from src/lib/types.ts rename to apps/web/src/lib/types.ts index 55c7c44b..cd20c0d3 100644 --- a/src/lib/types.ts +++ b/apps/web/src/lib/types.ts @@ -1,5 +1,6 @@ import type { Blob } from '@atcute/lexicons'; import type { AppBskyActorDefs } from '@atcute/bluesky'; +import type { Node } from '@blento/schema'; export type Item = { id: string; @@ -97,6 +98,13 @@ export type WebsiteData = { sections: SectionRecord[]; + /** The node graph (migrate-on-read). Source of truth going forward; render still uses cards/sections. */ + nodes?: Node[]; + + /** True when `nodes` came from stored app.blento.node records (page already migrated), vs rebuilt + * on read from legacy card/section. Save uses this to know whether to retire the legacy records. */ + migratedStorage?: boolean; + additionalData: Record; updatedAt: number; version?: number; diff --git a/src/lib/website/analytics/analytics.remote.ts b/apps/web/src/lib/website/analytics/analytics.remote.ts similarity index 100% rename from src/lib/website/analytics/analytics.remote.ts rename to apps/web/src/lib/website/analytics/analytics.remote.ts diff --git a/src/lib/website/data/context.ts b/apps/web/src/lib/website/data/context.ts similarity index 100% rename from src/lib/website/data/context.ts rename to apps/web/src/lib/website/data/context.ts diff --git a/src/lib/website/data/file-processing.ts b/apps/web/src/lib/website/data/file-processing.ts similarity index 100% rename from src/lib/website/data/file-processing.ts rename to apps/web/src/lib/website/data/file-processing.ts diff --git a/src/lib/website/data/load.ts b/apps/web/src/lib/website/data/load.ts similarity index 82% rename from src/lib/website/data/load.ts rename to apps/web/src/lib/website/data/load.ts index 8dd030ee..b010e7b6 100644 --- a/src/lib/website/data/load.ts +++ b/apps/web/src/lib/website/data/load.ts @@ -4,7 +4,7 @@ import { CardDefinitionsByType } from '$lib/cards'; import type { CacheService } from '$lib/helpers/cache'; import { createEmptyCard } from '$lib/helpers/items'; import type { Item, PronounsRecord, SectionRecord, WebsiteData } from '$lib/types'; -import { ensureSections } from '$lib/sections/migrate'; +import { buildGraph, nodesToItems, recordToNode, type Node, type NodeRecord } from '@blento/schema'; import { error } from '@sveltejs/kit'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; @@ -140,7 +140,7 @@ function loadCardFromContrail(did: Did, rkey: string, db: D1Database) { function loadFromContrail(actor: ActorIdentifier, db: D1Database, page: string) { return tryContrail('cards+pages+sections query', async () => { const client = getServerClient(db); - const [cardRes, pageRes, sectionRes] = await Promise.all([ + const [cardRes, pageRes, sectionRes, nodeRes] = await Promise.all([ client.get('app.blento.card.listRecords', { params: { actor, limit: 200, profiles: true, page } }), @@ -149,12 +149,28 @@ function loadFromContrail(actor: ActorIdentifier, db: D1Database, page: string) }), client.get('app.blento.section.listRecords' as any, { params: { actor, limit: 200, page } - }) as Promise + }) as Promise, + // Supplementary + tolerant: ok if contrail hasn't indexed app.blento.node yet (returns + // null) — then nodesFromRecords stays empty and we migrate-on-read from card/section. + ( + client.get('app.blento.node.listRecords' as any, { + params: { actor, limit: 200, profiles: true, page } + }) as Promise + ).catch(() => null) ]); if (!cardRes.ok) return null; - const cards = cardRes.data.records.map((r) => ({ ...(r.value as object) }) as Item); + const cards = cardRes.data.records.map( + (r) => ({ ...(r.value as object), id: parseUri(r.uri)?.rkey }) as Item + ); + + const nodes = + nodeRes?.ok && nodeRes.data?.records + ? (nodeRes.data.records as any[]).map((r: any) => + recordToNode(parseUri(r.uri)?.rkey ?? '', r.value as unknown as NodeRecord) + ) + : []; const pages = pageRes.ok ? pageRes.data.records @@ -175,9 +191,13 @@ function loadFromContrail(actor: ActorIdentifier, db: D1Database, page: string) return { cards, + nodes, pages, sections, - profiles: (cardRes.data.profiles ?? []) as ContrailProfile[] + // A fully migrated page has no card records → take profiles from the node query instead. + profiles: ((cardRes.data.profiles?.length + ? cardRes.data.profiles + : nodeRes?.data?.profiles) ?? []) as ContrailProfile[] }; }); } @@ -230,11 +250,16 @@ export async function loadData( let profile: WebsiteData['profile']; let publication: WebsiteData['publication'] | undefined; let pronounsRecord: PronounsRecord | undefined; + // New format. Empty = this page hasn't been migrated yet → fall back to card/section on read. + // (PDS path only for now; the contrail path keeps migrating card/section until contrail indexes + // app.blento.node — see Phase 1c notes.) + let nodesFromRecords: Node[] = []; if (contrailData) { cards = contrailData.cards; sectionRecords = contrailData.sections; pageRecords = contrailData.pages; + nodesFromRecords = contrailData.nodes ?? []; const hasBskyProfile = contrailData.profiles.some( (p) => p.did === did && p.collection === 'app.bsky.actor.profile' @@ -263,26 +288,38 @@ export async function loadData( } } else { // Fallback: no D1 available (e.g. vite dev) — use PDS directly - const [cardRecords, pageRecs, sectionRecs, mainPub, prof, pronouns] = await Promise.all([ - listRecords({ did, collection: 'app.blento.card', limit: 0 }).catch((e) => { - console.error('error getting records for collection app.blento.card', e); - return [] as Awaited>; - }), - listRecords({ did, collection: 'app.blento.page' }).catch( - () => [] as Awaited> - ), - listRecords({ did, collection: 'app.blento.section' }).catch( - () => [] as Awaited> - ), - getSelfPublicationFromPDS(did), - getDetailedProfile({ did }), - getPronounsFromPDS(did) - ]); + const [cardRecords, pageRecs, sectionRecs, nodeRecs, mainPub, prof, pronouns] = + await Promise.all([ + listRecords({ did, collection: 'app.blento.card', limit: 0 }).catch((e) => { + console.error('error getting records for collection app.blento.card', e); + return [] as Awaited>; + }), + listRecords({ did, collection: 'app.blento.page' }).catch( + () => [] as Awaited> + ), + listRecords({ did, collection: 'app.blento.section' }).catch( + () => [] as Awaited> + ), + listRecords({ did, collection: 'app.blento.node', limit: 0 }).catch( + () => [] as Awaited> + ), + getSelfPublicationFromPDS(did), + getDetailedProfile({ did }), + getPronounsFromPDS(did) + ]); - cards = cardRecords.map((v) => ({ ...v.value }) as Item); + // Scope to this page BEFORE building the graph: buildGraph adopts orphans into the current + // page and stamps node.page, so feeding it other pages' cards would both render them here and + // (via originalCards) delete their records on save. The contrail path filters server-side. + cards = cardRecords + .filter((v) => (v.value as Item | undefined)?.page === fullPage) + .map((v) => ({ ...v.value, id: parseUri(v.uri)?.rkey }) as Item); sectionRecords = sectionRecs .filter((v) => (v.value as any)?.page === fullPage) .map((v) => ({ ...(v.value as object), id: parseUri(v.uri)?.rkey }) as SectionRecord); + nodesFromRecords = nodeRecs + .filter((v) => (v.value as unknown as NodeRecord)?.page === fullPage) + .map((v) => recordToNode(parseUri(v.uri)?.rkey ?? '', v.value as unknown as NodeRecord)); pageRecords = pageRecs; profile = prof; publication = mainPub?.value as WebsiteData['publication'] | undefined; @@ -297,7 +334,14 @@ export async function loadData( publication ??= defaultPublication(profile); - const migrated = ensureSections(sectionRecords, cards, fullPage); + // Storage flows through the node graph. Dual-format read: prefer app.blento.node records; for + // not-yet-migrated pages, build the graph from card/section on read (no coord ladder — these + // records are already normalized). buildGraph subsumes ensureSections (synthesizes a grid + // container, adopts orphan cards). Either way, project back to v1-shaped sections/cards for render. + const graph = nodesFromRecords.length + ? nodesFromRecords + : buildGraph(sectionRecords, cards, fullPage, { order: 'input' }); + const migrated = nodesToItems(graph); const additionalData = await loadAdditionalData( migrated.cards, @@ -305,7 +349,7 @@ export async function loadData( env ); - return checkData({ + const result = checkData({ page: fullPage, handle, did, @@ -319,6 +363,13 @@ export async function loadData( updatedAt: Date.now(), version: 1 }); + // Attach the canonical node graph for downstream consumers: the stored records when present, + // else the migrate-on-read graph rebuilt from the post-collision-fix state. + result.nodes = nodesFromRecords.length + ? nodesFromRecords + : buildGraph(result.sections, result.cards, fullPage, { order: 'input' }); + result.migratedStorage = nodesFromRecords.length > 0; + return result; } export async function loadCardData( diff --git a/src/lib/website/edit/EditTopBar.svelte b/apps/web/src/lib/website/edit/EditTopBar.svelte similarity index 100% rename from src/lib/website/edit/EditTopBar.svelte rename to apps/web/src/lib/website/edit/EditTopBar.svelte diff --git a/src/lib/website/edit/EditableProfile.svelte b/apps/web/src/lib/website/edit/EditableProfile.svelte similarity index 100% rename from src/lib/website/edit/EditableProfile.svelte rename to apps/web/src/lib/website/edit/EditableProfile.svelte diff --git a/src/lib/website/edit/EditableWebsite.svelte b/apps/web/src/lib/website/edit/EditableWebsite.svelte similarity index 100% rename from src/lib/website/edit/EditableWebsite.svelte rename to apps/web/src/lib/website/edit/EditableWebsite.svelte diff --git a/src/lib/website/edit/InsertSectionButton.svelte b/apps/web/src/lib/website/edit/InsertSectionButton.svelte similarity index 100% rename from src/lib/website/edit/InsertSectionButton.svelte rename to apps/web/src/lib/website/edit/InsertSectionButton.svelte diff --git a/src/lib/website/edit/LayoutPanel.svelte b/apps/web/src/lib/website/edit/LayoutPanel.svelte similarity index 100% rename from src/lib/website/edit/LayoutPanel.svelte rename to apps/web/src/lib/website/edit/LayoutPanel.svelte diff --git a/src/lib/website/edit/MobileSelectionBar.svelte b/apps/web/src/lib/website/edit/MobileSelectionBar.svelte similarity index 100% rename from src/lib/website/edit/MobileSelectionBar.svelte rename to apps/web/src/lib/website/edit/MobileSelectionBar.svelte diff --git a/src/lib/website/edit/PageSwitcherBar.svelte b/apps/web/src/lib/website/edit/PageSwitcherBar.svelte similarity index 100% rename from src/lib/website/edit/PageSwitcherBar.svelte rename to apps/web/src/lib/website/edit/PageSwitcherBar.svelte diff --git a/src/lib/website/edit/SaveModal.svelte b/apps/web/src/lib/website/edit/SaveModal.svelte similarity index 100% rename from src/lib/website/edit/SaveModal.svelte rename to apps/web/src/lib/website/edit/SaveModal.svelte diff --git a/src/lib/website/edit/SectionPanel.svelte b/apps/web/src/lib/website/edit/SectionPanel.svelte similarity index 100% rename from src/lib/website/edit/SectionPanel.svelte rename to apps/web/src/lib/website/edit/SectionPanel.svelte diff --git a/src/lib/website/edit/SettingsSidebar.svelte b/apps/web/src/lib/website/edit/SettingsSidebar.svelte similarity index 100% rename from src/lib/website/edit/SettingsSidebar.svelte rename to apps/web/src/lib/website/edit/SettingsSidebar.svelte diff --git a/src/lib/website/settings/SettingsOverlay.svelte b/apps/web/src/lib/website/settings/SettingsOverlay.svelte similarity index 100% rename from src/lib/website/settings/SettingsOverlay.svelte rename to apps/web/src/lib/website/settings/SettingsOverlay.svelte diff --git a/src/lib/website/settings/sections/AccountSection.svelte b/apps/web/src/lib/website/settings/sections/AccountSection.svelte similarity index 100% rename from src/lib/website/settings/sections/AccountSection.svelte rename to apps/web/src/lib/website/settings/sections/AccountSection.svelte diff --git a/src/lib/website/settings/sections/AnalyticsSection.svelte b/apps/web/src/lib/website/settings/sections/AnalyticsSection.svelte similarity index 100% rename from src/lib/website/settings/sections/AnalyticsSection.svelte rename to apps/web/src/lib/website/settings/sections/AnalyticsSection.svelte diff --git a/src/lib/website/settings/sections/CustomDomainSection.svelte b/apps/web/src/lib/website/settings/sections/CustomDomainSection.svelte similarity index 100% rename from src/lib/website/settings/sections/CustomDomainSection.svelte rename to apps/web/src/lib/website/settings/sections/CustomDomainSection.svelte diff --git a/src/lib/website/settings/sections/LayoutSection.svelte b/apps/web/src/lib/website/settings/sections/LayoutSection.svelte similarity index 100% rename from src/lib/website/settings/sections/LayoutSection.svelte rename to apps/web/src/lib/website/settings/sections/LayoutSection.svelte diff --git a/src/lib/website/settings/sections/PageSection.svelte b/apps/web/src/lib/website/settings/sections/PageSection.svelte similarity index 100% rename from src/lib/website/settings/sections/PageSection.svelte rename to apps/web/src/lib/website/settings/sections/PageSection.svelte diff --git a/src/lib/website/view/ContextProvider.svelte b/apps/web/src/lib/website/view/ContextProvider.svelte similarity index 100% rename from src/lib/website/view/ContextProvider.svelte rename to apps/web/src/lib/website/view/ContextProvider.svelte diff --git a/src/lib/website/view/EmptyState.svelte b/apps/web/src/lib/website/view/EmptyState.svelte similarity index 100% rename from src/lib/website/view/EmptyState.svelte rename to apps/web/src/lib/website/view/EmptyState.svelte diff --git a/src/lib/website/view/Head.svelte b/apps/web/src/lib/website/view/Head.svelte similarity index 100% rename from src/lib/website/view/Head.svelte rename to apps/web/src/lib/website/view/Head.svelte diff --git a/src/lib/website/view/MadeWithBlento.svelte b/apps/web/src/lib/website/view/MadeWithBlento.svelte similarity index 100% rename from src/lib/website/view/MadeWithBlento.svelte rename to apps/web/src/lib/website/view/MadeWithBlento.svelte diff --git a/src/lib/website/view/Profile.svelte b/apps/web/src/lib/website/view/Profile.svelte similarity index 100% rename from src/lib/website/view/Profile.svelte rename to apps/web/src/lib/website/view/Profile.svelte diff --git a/src/lib/website/view/ThemeScript.svelte b/apps/web/src/lib/website/view/ThemeScript.svelte similarity index 100% rename from src/lib/website/view/ThemeScript.svelte rename to apps/web/src/lib/website/view/ThemeScript.svelte diff --git a/src/lib/website/view/Website.svelte b/apps/web/src/lib/website/view/Website.svelte similarity index 100% rename from src/lib/website/view/Website.svelte rename to apps/web/src/lib/website/view/Website.svelte diff --git a/src/params/actor.ts b/apps/web/src/params/actor.ts similarity index 100% rename from src/params/actor.ts rename to apps/web/src/params/actor.ts diff --git a/src/routes/(auth)/login/+page.server.ts b/apps/web/src/routes/(auth)/login/+page.server.ts similarity index 100% rename from src/routes/(auth)/login/+page.server.ts rename to apps/web/src/routes/(auth)/login/+page.server.ts diff --git a/src/routes/(auth)/login/+page.svelte b/apps/web/src/routes/(auth)/login/+page.svelte similarity index 100% rename from src/routes/(auth)/login/+page.svelte rename to apps/web/src/routes/(auth)/login/+page.svelte diff --git a/src/routes/(auth)/oauth-client-metadata.json/+server.ts b/apps/web/src/routes/(auth)/oauth-client-metadata.json/+server.ts similarity index 100% rename from src/routes/(auth)/oauth-client-metadata.json/+server.ts rename to apps/web/src/routes/(auth)/oauth-client-metadata.json/+server.ts diff --git a/src/routes/(auth)/oauth/callback/+server.ts b/apps/web/src/routes/(auth)/oauth/callback/+server.ts similarity index 100% rename from src/routes/(auth)/oauth/callback/+server.ts rename to apps/web/src/routes/(auth)/oauth/callback/+server.ts diff --git a/src/routes/(auth)/oauth/jwks.json/+server.ts b/apps/web/src/routes/(auth)/oauth/jwks.json/+server.ts similarity index 100% rename from src/routes/(auth)/oauth/jwks.json/+server.ts rename to apps/web/src/routes/(auth)/oauth/jwks.json/+server.ts diff --git a/src/routes/(legal)/+layout.svelte b/apps/web/src/routes/(legal)/+layout.svelte similarity index 100% rename from src/routes/(legal)/+layout.svelte rename to apps/web/src/routes/(legal)/+layout.svelte diff --git a/src/routes/(legal)/imprint/+page.svelte b/apps/web/src/routes/(legal)/imprint/+page.svelte similarity index 100% rename from src/routes/(legal)/imprint/+page.svelte rename to apps/web/src/routes/(legal)/imprint/+page.svelte diff --git a/src/routes/(legal)/privacy/+page.svelte b/apps/web/src/routes/(legal)/privacy/+page.svelte similarity index 100% rename from src/routes/(legal)/privacy/+page.svelte rename to apps/web/src/routes/(legal)/privacy/+page.svelte diff --git a/src/routes/(legal)/terms/+page.svelte b/apps/web/src/routes/(legal)/terms/+page.svelte similarity index 100% rename from src/routes/(legal)/terms/+page.svelte rename to apps/web/src/routes/(legal)/terms/+page.svelte diff --git a/src/routes/+error.svelte b/apps/web/src/routes/+error.svelte similarity index 100% rename from src/routes/+error.svelte rename to apps/web/src/routes/+error.svelte diff --git a/src/routes/+layout.server.ts b/apps/web/src/routes/+layout.server.ts similarity index 100% rename from src/routes/+layout.server.ts rename to apps/web/src/routes/+layout.server.ts diff --git a/src/routes/+layout.svelte b/apps/web/src/routes/+layout.svelte similarity index 100% rename from src/routes/+layout.svelte rename to apps/web/src/routes/+layout.svelte diff --git a/src/routes/[[actor=actor]]/(pages)/+layout.server.ts b/apps/web/src/routes/[[actor=actor]]/(pages)/+layout.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/+layout.server.ts rename to apps/web/src/routes/[[actor=actor]]/(pages)/+layout.server.ts diff --git a/src/routes/[[actor=actor]]/(pages)/+page.svelte b/apps/web/src/routes/[[actor=actor]]/(pages)/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/(pages)/+page.svelte diff --git a/src/routes/[[actor=actor]]/(pages)/edit/+page.svelte b/apps/web/src/routes/[[actor=actor]]/(pages)/edit/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/edit/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/(pages)/edit/+page.svelte diff --git a/src/routes/[[actor=actor]]/(pages)/p/[[page]]/+page.svelte b/apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/p/[[page]]/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/+page.svelte diff --git a/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte b/apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte diff --git a/src/routes/[[actor=actor]]/(pages)/p/[[page]]/edit/+page.svelte b/apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/edit/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/(pages)/p/[[page]]/edit/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/(pages)/p/[[page]]/edit/+page.svelte diff --git a/src/routes/[[actor=actor]]/.well-known/atproto-did/+server.ts b/apps/web/src/routes/[[actor=actor]]/.well-known/atproto-did/+server.ts similarity index 100% rename from src/routes/[[actor=actor]]/.well-known/atproto-did/+server.ts rename to apps/web/src/routes/[[actor=actor]]/.well-known/atproto-did/+server.ts diff --git a/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts b/apps/web/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts similarity index 100% rename from src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts rename to apps/web/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts diff --git a/src/routes/[[actor=actor]]/blog/+layout.server.ts b/apps/web/src/routes/[[actor=actor]]/blog/+layout.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/blog/+layout.server.ts rename to apps/web/src/routes/[[actor=actor]]/blog/+layout.server.ts diff --git a/src/routes/[[actor=actor]]/blog/+layout.svelte b/apps/web/src/routes/[[actor=actor]]/blog/+layout.svelte similarity index 100% rename from src/routes/[[actor=actor]]/blog/+layout.svelte rename to apps/web/src/routes/[[actor=actor]]/blog/+layout.svelte diff --git a/src/routes/[[actor=actor]]/blog/+page.server.ts b/apps/web/src/routes/[[actor=actor]]/blog/+page.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/blog/+page.server.ts rename to apps/web/src/routes/[[actor=actor]]/blog/+page.server.ts diff --git a/src/routes/[[actor=actor]]/blog/+page.svelte b/apps/web/src/routes/[[actor=actor]]/blog/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/blog/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/blog/+page.svelte diff --git a/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts b/apps/web/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts rename to apps/web/src/routes/[[actor=actor]]/blog/[rkey]/+page.server.ts diff --git a/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte b/apps/web/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/blog/[rkey]/+page.svelte diff --git a/src/routes/[[actor=actor]]/blog/new/+page.svelte b/apps/web/src/routes/[[actor=actor]]/blog/new/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/blog/new/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/blog/new/+page.svelte diff --git a/src/routes/[[actor=actor]]/event/create/+page.server.ts b/apps/web/src/routes/[[actor=actor]]/event/create/+page.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/event/create/+page.server.ts rename to apps/web/src/routes/[[actor=actor]]/event/create/+page.server.ts diff --git a/src/routes/[[actor=actor]]/event/create/+page.svelte b/apps/web/src/routes/[[actor=actor]]/event/create/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/event/create/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/event/create/+page.svelte diff --git a/src/routes/[[actor=actor]]/event/r/[rkey]/+page.server.ts b/apps/web/src/routes/[[actor=actor]]/event/r/[rkey]/+page.server.ts similarity index 100% rename from src/routes/[[actor=actor]]/event/r/[rkey]/+page.server.ts rename to apps/web/src/routes/[[actor=actor]]/event/r/[rkey]/+page.server.ts diff --git a/src/routes/[[actor=actor]]/event/r/[rkey]/+page.svelte b/apps/web/src/routes/[[actor=actor]]/event/r/[rkey]/+page.svelte similarity index 100% rename from src/routes/[[actor=actor]]/event/r/[rkey]/+page.svelte rename to apps/web/src/routes/[[actor=actor]]/event/r/[rkey]/+page.svelte diff --git a/src/routes/[[actor=actor]]/og-new.png/+server.ts b/apps/web/src/routes/[[actor=actor]]/og-new.png/+server.ts similarity index 100% rename from src/routes/[[actor=actor]]/og-new.png/+server.ts rename to apps/web/src/routes/[[actor=actor]]/og-new.png/+server.ts diff --git a/src/routes/[[actor=actor]]/refresh/+server.ts b/apps/web/src/routes/[[actor=actor]]/refresh/+server.ts similarity index 100% rename from src/routes/[[actor=actor]]/refresh/+server.ts rename to apps/web/src/routes/[[actor=actor]]/refresh/+server.ts diff --git a/src/routes/api/activate-domain/+server.ts b/apps/web/src/routes/api/activate-domain/+server.ts similarity index 100% rename from src/routes/api/activate-domain/+server.ts rename to apps/web/src/routes/api/activate-domain/+server.ts diff --git a/src/routes/api/cron/+server.ts b/apps/web/src/routes/api/cron/+server.ts similarity index 100% rename from src/routes/api/cron/+server.ts rename to apps/web/src/routes/api/cron/+server.ts diff --git a/src/routes/api/geocoding/+server.ts b/apps/web/src/routes/api/geocoding/+server.ts similarity index 100% rename from src/routes/api/geocoding/+server.ts rename to apps/web/src/routes/api/geocoding/+server.ts diff --git a/src/routes/api/image-proxy/+server.ts b/apps/web/src/routes/api/image-proxy/+server.ts similarity index 100% rename from src/routes/api/image-proxy/+server.ts rename to apps/web/src/routes/api/image-proxy/+server.ts diff --git a/src/routes/api/instagram/info/+server.ts b/apps/web/src/routes/api/instagram/info/+server.ts similarity index 100% rename from src/routes/api/instagram/info/+server.ts rename to apps/web/src/routes/api/instagram/info/+server.ts diff --git a/src/routes/api/links/+server.ts b/apps/web/src/routes/api/links/+server.ts similarity index 100% rename from src/routes/api/links/+server.ts rename to apps/web/src/routes/api/links/+server.ts diff --git a/src/routes/api/verify-domain/+server.ts b/apps/web/src/routes/api/verify-domain/+server.ts similarity index 100% rename from src/routes/api/verify-domain/+server.ts rename to apps/web/src/routes/api/verify-domain/+server.ts diff --git a/src/routes/embed-test/+page.server.ts b/apps/web/src/routes/embed-test/+page.server.ts similarity index 100% rename from src/routes/embed-test/+page.server.ts rename to apps/web/src/routes/embed-test/+page.server.ts diff --git a/src/routes/embed-test/+page.svelte b/apps/web/src/routes/embed-test/+page.svelte similarity index 100% rename from src/routes/embed-test/+page.svelte rename to apps/web/src/routes/embed-test/+page.svelte diff --git a/src/routes/test-cards/+layout.ts b/apps/web/src/routes/test-cards/+layout.ts similarity index 100% rename from src/routes/test-cards/+layout.ts rename to apps/web/src/routes/test-cards/+layout.ts diff --git a/src/routes/test-cards/+page.svelte b/apps/web/src/routes/test-cards/+page.svelte similarity index 100% rename from src/routes/test-cards/+page.svelte rename to apps/web/src/routes/test-cards/+page.svelte diff --git a/src/routes/test-cards/build-data.ts b/apps/web/src/routes/test-cards/build-data.ts similarity index 100% rename from src/routes/test-cards/build-data.ts rename to apps/web/src/routes/test-cards/build-data.ts diff --git a/src/routes/test-cards/edit/+page.svelte b/apps/web/src/routes/test-cards/edit/+page.svelte similarity index 100% rename from src/routes/test-cards/edit/+page.svelte rename to apps/web/src/routes/test-cards/edit/+page.svelte diff --git a/src/routes/xrpc/[...path]/+server.ts b/apps/web/src/routes/xrpc/[...path]/+server.ts similarity index 100% rename from src/routes/xrpc/[...path]/+server.ts rename to apps/web/src/routes/xrpc/[...path]/+server.ts diff --git a/static/dino/Instructions.url b/apps/web/static/dino/Instructions.url similarity index 100% rename from static/dino/Instructions.url rename to apps/web/static/dino/Instructions.url diff --git a/static/dino/License.txt b/apps/web/static/dino/License.txt similarity index 100% rename from static/dino/License.txt rename to apps/web/static/dino/License.txt diff --git a/static/dino/Preview.png b/apps/web/static/dino/Preview.png similarity index 100% rename from static/dino/Preview.png rename to apps/web/static/dino/Preview.png diff --git a/static/dino/Sample.png b/apps/web/static/dino/Sample.png similarity index 100% rename from static/dino/Sample.png rename to apps/web/static/dino/Sample.png diff --git a/static/dino/Tilemap/monochrome_tilemap.png b/apps/web/static/dino/Tilemap/monochrome_tilemap.png similarity index 100% rename from static/dino/Tilemap/monochrome_tilemap.png rename to apps/web/static/dino/Tilemap/monochrome_tilemap.png diff --git a/static/dino/Tilemap/monochrome_tilemap_packed.png b/apps/web/static/dino/Tilemap/monochrome_tilemap_packed.png similarity index 100% rename from static/dino/Tilemap/monochrome_tilemap_packed.png rename to apps/web/static/dino/Tilemap/monochrome_tilemap_packed.png diff --git a/static/dino/Tilemap/monochrome_tilemap_transparent.png b/apps/web/static/dino/Tilemap/monochrome_tilemap_transparent.png similarity index 100% rename from static/dino/Tilemap/monochrome_tilemap_transparent.png rename to apps/web/static/dino/Tilemap/monochrome_tilemap_transparent.png diff --git a/static/dino/Tilemap/monochrome_tilemap_transparent_packed.png b/apps/web/static/dino/Tilemap/monochrome_tilemap_transparent_packed.png similarity index 100% rename from static/dino/Tilemap/monochrome_tilemap_transparent_packed.png rename to apps/web/static/dino/Tilemap/monochrome_tilemap_transparent_packed.png diff --git a/static/dino/Tilesheet.txt b/apps/web/static/dino/Tilesheet.txt similarity index 100% rename from static/dino/Tilesheet.txt rename to apps/web/static/dino/Tilesheet.txt diff --git a/static/dino/cells.txt b/apps/web/static/dino/cells.txt similarity index 100% rename from static/dino/cells.txt rename to apps/web/static/dino/cells.txt diff --git a/static/embed/v0/sdk.js b/apps/web/static/embed/v0/sdk.js similarity index 100% rename from static/embed/v0/sdk.js rename to apps/web/static/embed/v0/sdk.js diff --git a/static/embed/v0/test.html b/apps/web/static/embed/v0/test.html similarity index 100% rename from static/embed/v0/test.html rename to apps/web/static/embed/v0/test.html diff --git a/static/favicon.ico b/apps/web/static/favicon.ico similarity index 100% rename from static/favicon.ico rename to apps/web/static/favicon.ico diff --git a/static/robots.txt b/apps/web/static/robots.txt similarity index 100% rename from static/robots.txt rename to apps/web/static/robots.txt diff --git a/svelte.config.js b/apps/web/svelte.config.js similarity index 100% rename from svelte.config.js rename to apps/web/svelte.config.js diff --git a/tsconfig.json b/apps/web/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/web/tsconfig.json diff --git a/vite.config.ts b/apps/web/vite.config.ts similarity index 85% rename from vite.config.ts rename to apps/web/vite.config.ts index 4fff4e8d..cd47acb9 100644 --- a/vite.config.ts +++ b/apps/web/vite.config.ts @@ -12,6 +12,10 @@ export default defineConfig({ resolve: { dedupe: ['bits-ui', '@internationalized/date', 'svelte'] }, + // Workspace packages export raw .ts; force Vite to transform them for SSR + the CF build. + ssr: { + noExternal: [/^@blento\//] + }, server: { host: '127.0.0.1', port: 5179 diff --git a/vitest.config.ts b/apps/web/vitest.config.ts similarity index 100% rename from vitest.config.ts rename to apps/web/vitest.config.ts diff --git a/wrangler.jsonc b/apps/web/wrangler.jsonc similarity index 56% rename from wrangler.jsonc rename to apps/web/wrangler.jsonc index 9670f28e..576dfcb7 100644 --- a/wrangler.jsonc +++ b/apps/web/wrangler.jsonc @@ -72,5 +72,45 @@ ], "triggers": { "crons": ["*/5 * * * *"] + }, + + /** + * Staging env for the `migration` branch — the diff-harness target. + * deploy: pnpm --filter @blento/web exec wrangler deploy --env staging + * + * It reuses prod's READ bindings (cache / custom-domains / contrail D1) so the harness diffs the + * SAME data through the new code. No cron, and a separate analytics dataset so harness snapshots + * don't pollute prod. CAVEAT: it shares prod KV/D1 — staging must not receive edit/write traffic + * (writes would hit prod). For full isolation, point these at separate staging namespaces + a D1 + * copy. Adjust the domain/ids to your setup. + */ + "env": { + "staging": { + "name": "blento-staging", + "vars": { + "PUBLIC_HANDLE": "blento.app", + "PUBLIC_IS_SELFHOSTED": "", + "PUBLIC_DOMAIN": "https://staging.blento.app", + "PUBLIC_GIPHY_API_TOKEN": "ltXijv1bkNPrEgnpJ0tIdLWXjnAeE7bL" + }, + "routes": [{ "pattern": "staging.blento.app", "custom_domain": true }], + "kv_namespaces": [ + { "binding": "USER_DATA_CACHE", "id": "d6ff203259de48538d332b0a5df258a7" }, + { "binding": "CUSTOM_DOMAINS", "id": "f449b3b5c8a349478405e2c04ed265f0" }, + { "binding": "OAUTH_SESSIONS", "id": "4d872b4630a042d69ecd63f87d6b2094" }, + { "binding": "OAUTH_STATES", "id": "c644b498d4af4c5892b3106d52c7760a" } + ], + "d1_databases": [ + { + "binding": "DB", + "database_name": "blento-v3", + "database_id": "83e7be08-8503-4b54-9f73-0bac105761b5", + "remote": false + } + ], + "analytics_engine_datasets": [ + { "binding": "ANALYTICS", "dataset": "blento_staging_pageviews" } + ] + } } } diff --git a/lex.config.js b/lex.config.js deleted file mode 100644 index 72900701..00000000 --- a/lex.config.js +++ /dev/null @@ -1,24 +0,0 @@ -import { defineLexiconConfig } from '@atcute/lex-cli'; - -export default defineLexiconConfig({ - files: ['lexicons/custom/**/*.json', 'lexicons/pulled/**/*.json', 'lexicons/generated/**/*.json'], - outdir: 'src/lexicon-types/', - imports: ['@atcute/atproto'], - pull: { - outdir: 'lexicons/pulled/', - sources: [ - { - type: 'atproto', - mode: 'nsids', - nsids: [ - 'app.blento.card', - 'app.blento.page', - 'app.blento.section', - 'app.bsky.actor.profile', - 'app.nearhorizon.actor.pronouns', - 'site.standard.publication' - ] - } - ] - } -}); diff --git a/lexicons/generated/app/blento/authFull.json b/lexicons/generated/app/blento/authFull.json deleted file mode 100644 index 70cf8fc2..00000000 --- a/lexicons/generated/app/blento/authFull.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.authFull", - "defs": { - "main": { - "type": "permission-set", - "title": "app.blento", - "description": "Full access to the app.blento service", - "permissions": [ - { - "type": "permission", - "resource": "rpc", - "aud": "*", - "lxm": [ - "app.blento.card.getRecord", - "app.blento.card.listRecords", - "app.blento.getCursor", - "app.blento.getOverview", - "app.blento.getProfile", - "app.blento.notifyOfUpdate", - "app.blento.page.getRecord", - "app.blento.page.listRecords", - "app.blento.section.getRecord", - "app.blento.section.listRecords" - ] - }, - { - "type": "permission", - "resource": "repo", - "collection": ["app.blento.card", "app.blento.page", "app.blento.section"] - } - ] - } - } -} diff --git a/lexicons/generated/app/blento/card/getRecord.json b/lexicons/generated/app/blento/card/getRecord.json deleted file mode 100644 index df2ff67f..00000000 --- a/lexicons/generated/app/blento/card/getRecord.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.card.getRecord", - "defs": { - "main": { - "type": "query", - "description": "Get a single app.blento.card record by AT URI", - "parameters": { - "type": "params", - "required": ["uri"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "AT URI of the record" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["uri", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.card#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/card/listRecords.json b/lexicons/generated/app/blento/card/listRecords.json deleted file mode 100644 index ae25dd66..00000000 --- a/lexicons/generated/app/blento/card/listRecords.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.card.listRecords", - "defs": { - "main": { - "type": "query", - "description": "Query app.blento.card records with filters", - "parameters": { - "type": "params", - "properties": { - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "cursor": { - "type": "string" - }, - "actor": { - "type": "string", - "format": "at-identifier", - "description": "Filter by DID or handle (triggers on-demand backfill)" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - }, - "page": { - "type": "string", - "description": "Filter by page" - }, - "cardType": { - "type": "string", - "description": "Filter by cardType" - }, - "sort": { - "type": "string", - "knownValues": ["page", "cardType"], - "description": "Field to sort by (default: time_us)" - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["records"], - "properties": { - "records": { - "type": "array", - "items": { - "type": "ref", - "ref": "#record" - } - }, - "cursor": { - "type": "string" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "record": { - "type": "object", - "required": ["uri", "cid", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.card#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/getCursor.json b/lexicons/generated/app/blento/getCursor.json deleted file mode 100644 index a94e9f60..00000000 --- a/lexicons/generated/app/blento/getCursor.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.getCursor", - "defs": { - "main": { - "type": "query", - "description": "Get the current cursor position", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "time_us": { - "type": "integer" - }, - "date": { - "type": "string" - }, - "seconds_ago": { - "type": "integer" - } - } - } - } - } - } -} diff --git a/lexicons/generated/app/blento/getOverview.json b/lexicons/generated/app/blento/getOverview.json deleted file mode 100644 index 015c067d..00000000 --- a/lexicons/generated/app/blento/getOverview.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.getOverview", - "defs": { - "main": { - "type": "query", - "description": "Get an overview of all indexed collections", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["total_records", "collections"], - "properties": { - "total_records": { - "type": "integer" - }, - "collections": { - "type": "array", - "items": { - "type": "ref", - "ref": "#collectionStats" - } - } - } - } - } - }, - "collectionStats": { - "type": "object", - "required": ["collection", "records", "unique_users"], - "properties": { - "collection": { - "type": "string" - }, - "records": { - "type": "integer" - }, - "unique_users": { - "type": "integer" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/getProfile.json b/lexicons/generated/app/blento/getProfile.json deleted file mode 100644 index 482aed68..00000000 --- a/lexicons/generated/app/blento/getProfile.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.getProfile", - "defs": { - "main": { - "type": "query", - "description": "Get a user's profiles by DID or handle", - "parameters": { - "type": "params", - "required": ["actor"], - "properties": { - "actor": { - "type": "string", - "format": "at-identifier", - "description": "DID or handle of the user" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["profiles"], - "properties": { - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/notifyOfUpdate.json b/lexicons/generated/app/blento/notifyOfUpdate.json deleted file mode 100644 index 4c4b923a..00000000 --- a/lexicons/generated/app/blento/notifyOfUpdate.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.notifyOfUpdate", - "defs": { - "main": { - "type": "procedure", - "description": "Notify of a record change for immediate indexing. Fetches the record from the user's PDS and indexes (or deletes) it.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "Single AT URI to fetch and index" - }, - "uris": { - "type": "array", - "items": { - "type": "string", - "format": "at-uri" - }, - "maxLength": 25, - "description": "Batch of AT URIs to fetch and index (max 25)" - } - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["indexed", "deleted"], - "properties": { - "indexed": { - "type": "integer", - "description": "Number of records created or updated" - }, - "deleted": { - "type": "integer", - "description": "Number of records deleted (not found on PDS)" - }, - "errors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Errors for individual URIs that could not be processed" - } - } - } - } - } - } -} diff --git a/lexicons/generated/app/blento/page/getRecord.json b/lexicons/generated/app/blento/page/getRecord.json deleted file mode 100644 index e57d73bd..00000000 --- a/lexicons/generated/app/blento/page/getRecord.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.page.getRecord", - "defs": { - "main": { - "type": "query", - "description": "Get a single app.blento.page record by AT URI", - "parameters": { - "type": "params", - "required": ["uri"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "AT URI of the record" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["uri", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.page#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/page/listRecords.json b/lexicons/generated/app/blento/page/listRecords.json deleted file mode 100644 index c912c319..00000000 --- a/lexicons/generated/app/blento/page/listRecords.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.page.listRecords", - "defs": { - "main": { - "type": "query", - "description": "Query app.blento.page records with filters", - "parameters": { - "type": "params", - "properties": { - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "cursor": { - "type": "string" - }, - "actor": { - "type": "string", - "format": "at-identifier", - "description": "Filter by DID or handle (triggers on-demand backfill)" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["records"], - "properties": { - "records": { - "type": "array", - "items": { - "type": "ref", - "ref": "#record" - } - }, - "cursor": { - "type": "string" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "record": { - "type": "object", - "required": ["uri", "cid", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.page#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/section/getRecord.json b/lexicons/generated/app/blento/section/getRecord.json deleted file mode 100644 index 2d5a172c..00000000 --- a/lexicons/generated/app/blento/section/getRecord.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.section.getRecord", - "defs": { - "main": { - "type": "query", - "description": "Get a single app.blento.section record by AT URI", - "parameters": { - "type": "params", - "required": ["uri"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "AT URI of the record" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["uri", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.section#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/app/blento/section/listRecords.json b/lexicons/generated/app/blento/section/listRecords.json deleted file mode 100644 index e384c7d9..00000000 --- a/lexicons/generated/app/blento/section/listRecords.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "lexicon": 1, - "id": "app.blento.section.listRecords", - "defs": { - "main": { - "type": "query", - "description": "Query app.blento.section records with filters", - "parameters": { - "type": "params", - "properties": { - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "cursor": { - "type": "string" - }, - "actor": { - "type": "string", - "format": "at-identifier", - "description": "Filter by DID or handle (triggers on-demand backfill)" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - }, - "page": { - "type": "string", - "description": "Filter by page" - }, - "sectionType": { - "type": "string", - "description": "Filter by sectionType" - }, - "sort": { - "type": "string", - "knownValues": ["page", "sectionType"], - "description": "Field to sort by (default: time_us)" - }, - "order": { - "type": "string", - "knownValues": ["asc", "desc"], - "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["records"], - "properties": { - "records": { - "type": "array", - "items": { - "type": "ref", - "ref": "#record" - } - }, - "cursor": { - "type": "string" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "record": { - "type": "object", - "required": ["uri", "cid", "value", "did", "collection", "rkey", "time_us"], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "ref", - "ref": "app.blento.section#main" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "time_us": { - "type": "integer" - } - } - }, - "profileEntry": { - "type": "object", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "cid": { - "type": "string", - "format": "cid" - }, - "value": { - "type": "unknown" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - } - } - } - } -} diff --git a/lexicons/generated/index.ts b/lexicons/generated/index.ts deleted file mode 100644 index b9515073..00000000 --- a/lexicons/generated/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Auto-generated by @atmo-dev/contrail-lexicons. Do not edit. -// Pass `lexicons` to `createWorker(config, { lexicons })` to expose them -// at `/xrpc/.lexicons` for consumer apps to typegen against. - -import _0 from '../custom/app/blento/card.json'; -import _1 from '../custom/app/blento/page.json'; -import _2 from '../custom/app/blento/section.json'; -import _3 from './app/blento/authFull.json'; -import _4 from './app/blento/card/getRecord.json'; -import _5 from './app/blento/card/listRecords.json'; -import _6 from './app/blento/getCursor.json'; -import _7 from './app/blento/getOverview.json'; -import _8 from './app/blento/getProfile.json'; -import _9 from './app/blento/notifyOfUpdate.json'; -import _10 from './app/blento/page/getRecord.json'; -import _11 from './app/blento/page/listRecords.json'; -import _12 from './app/blento/section/getRecord.json'; -import _13 from './app/blento/section/listRecords.json'; - -export const lexicons: object[] = [_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13]; diff --git a/package.json b/package.json index e1af4edc..bac1840c 100644 --- a/package.json +++ b/package.json @@ -3,128 +3,24 @@ "private": true, "version": "0.2.0", "type": "module", + "packageManager": "pnpm@11.2.2", + "engines": { + "node": ">=22" + }, "scripts": { - "dev": "vite dev", - "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build && contrail append-scheduled", - "preview": "pnpm run build && wrangler dev", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "eslint --fix . && prettier --write .", - "test": "vitest run", - "deploy": "pnpm run build && wrangler deploy", - "cf-typegen": "wrangler types ./src/worker-configuration.d.ts", - "env:generate-key": "npx tsx src/lib/atproto/scripts/generate-key.ts", - "env:generate-secret": "npx tsx src/lib/atproto/scripts/generate-secret.ts", - "env:setup-dev": "npx tsx src/lib/atproto/scripts/setup-dev.ts", - "tunnel": "npx tsx src/lib/atproto/scripts/tunnel.ts", - "generate": "contrail-lex generate", - "backfill": "contrail backfill", - "backfill:remote": "contrail backfill --remote" + "dev": "turbo run dev", + "web": "pnpm --filter @blento/web dev", + "build": "turbo run build", + "check": "turbo run check", + "lint": "turbo run lint", + "format": "prettier --write .", + "test": "turbo run test", + "deploy": "pnpm --filter @blento/web deploy" }, "devDependencies": { - "@atcute/lex-cli": "^2.8.1", - "@atcute/lexicon-doc": "^2.1.2", - "@atmo-dev/contrail-lexicons": "^0.4.5", - "@eslint/compat": "^2.0.3", - "@eslint/js": "^10.0.1", - "@sveltejs/adapter-cloudflare": "^7.2.8", - "@sveltejs/kit": "^2.55.0", - "@sveltejs/vite-plugin-svelte": "^7.0.0", - "@tailwindcss/forms": "^0.5.11", - "@tailwindcss/vite": "^4.2.1", - "@types/node": "^25.9.1", - "@types/turndown": "^5.0.6", - "eslint": "^10.0.3", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-svelte": "^3.15.2", - "globals": "^17.4.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.5.1", "prettier-plugin-tailwindcss": "^0.7.2", - "svelte": "^5.53.11", - "svelte-check": "^4.4.5", - "svelte-maplibre-gl": "^1.0.3", - "tailwindcss": "^4.2.1", - "tsx": "^4.21.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.57.0", - "valibot": "^1.3.1", - "vite": "^8.0.0", - "vitest": "^4.1.4" - }, - "dependencies": { - "@atcute/atproto": "^3.1.10", - "@atcute/bluesky": "^3.3.0", - "@atcute/bluesky-richtext-parser": "^2.1.1", - "@atcute/bluesky-richtext-segmenter": "^3.0.0", - "@atcute/client": "^4.2.1", - "@atcute/identity-resolver": "^1.2.2", - "@atcute/lexicons": "^1.3.0", - "@atcute/oauth-browser-client": "^3.0.0", - "@atcute/oauth-node-client": "^1.1.0", - "@atcute/standard-site": "^1.0.1", - "@atcute/tid": "^1.1.2", - "@atmo-dev/contrail": "^0.5.0", - "@atmo-dev/events-ui": "^0.1.0", - "@cloudflare/workers-types": "^4.20260313.1", - "@ethercorps/sveltekit-og": "^4.2.1", - "@floating-ui/dom": "^1.7.6", - "@foxui/3d": "^0.8.5", - "@foxui/colors": "^0.8.5", - "@foxui/core": "^0.9.1", - "@foxui/social": "^0.8.9", - "@foxui/text": "^0.8.5", - "@foxui/time": "^0.8.5", - "@foxui/visual": "^0.8.7", - "@internationalized/date": "^3.12.0", - "@number-flow/svelte": "^0.4.0", - "@tailwindcss/typography": "^0.5.19", - "@threlte/core": "^8.4.1", - "@threlte/extras": "^9.8.1", - "@tiptap/core": "^3.20.1", - "@tiptap/extension-bubble-menu": "^3.20.1", - "@tiptap/extension-code-block-lowlight": "^3.20.1", - "@tiptap/extension-document": "^3.20.1", - "@tiptap/extension-image": "^3.20.1", - "@tiptap/extension-link": "^3.20.1", - "@tiptap/extension-paragraph": "^3.20.1", - "@tiptap/extension-placeholder": "^3.20.1", - "@tiptap/extension-text": "^3.20.1", - "@tiptap/extension-typography": "^3.20.1", - "@tiptap/extension-underline": "^3.20.1", - "@tiptap/markdown": "^3.20.1", - "@tiptap/pm": "^3.20.1", - "@tiptap/starter-kit": "^3.20.1", - "@tiptap/suggestion": "^3.20.1", - "@types/three": "^0.183.1", - "bits-ui": "^2.16.3", - "clsx": "^2.1.1", - "dompurify": "^3.3.3", - "fast-xml-parser": "^5.9.3", - "gsap": "^3.14.2", - "hls.js": "^1.6.15", - "leaflet": "^1.9.4", - "link-preview-js": "^4.0.0", - "lowlight": "^3.3.0", - "maplibre-gl": "^5.20.0", - "marked": "^17.0.4", - "perfect-freehand": "^1.2.3", - "plyr": "^3.8.4", - "qr-code-styling": "^1.9.2", - "react-grid-layout": "^2.2.2", - "simple-icons": "^16.11.0", - "svelte-boring-avatars": "^1.2.6", - "svelte-sonner": "^1.1.0", - "svelte-tiptap": "^3.0.1", - "tailwind-merge": "^3.5.0", - "tailwind-variants": "^3.2.2", - "tailwindcss-animate": "^1.0.7", - "three": "^0.183.2", - "turndown": "^7.2.2", - "wrangler": "^4.73.0" - }, - "packageManager": "pnpm@11.2.2", - "license": "MIT" + "turbo": "^2.5.0" + } } diff --git a/packages/backup/backup.ts b/packages/backup/backup.ts new file mode 100644 index 00000000..dfad9823 --- /dev/null +++ b/packages/backup/backup.ts @@ -0,0 +1,228 @@ +/** + * Back up every blento repo's records (and referenced blobs) to disk. + * + * pnpm --filter @blento/backup start [outDir] + * + * Layout: + * ////.json — one JSON file per record + * ///_blobs/ — every blob the records reference + * + * Repos are discovered with com.atproto.sync.listReposByCollection on a relay (the same mechanism + * contrail uses to discover repos), unioned over the blento content collections — so it finds both + * legacy (card) and migrated (node) users. Per-repo records come straight from each repo's PDS. + * + * Env: + * BLENTO_RELAY relay host for discovery (default https://relay1.us-west.bsky.network) + * BLENTO_BACKUP_LIMIT cap the number of repos (for a quick test run); default = all + * BLENTO_BACKUP_CONCURRENCY repos fetched in parallel (default 12) + */ +import { mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +const RELAY = process.env.BLENTO_RELAY ?? 'https://relay1.us-west.bsky.network'; +const OUT_ROOT = process.argv[2] ?? './backups'; +const LIMIT = process.env.BLENTO_BACKUP_LIMIT ? Number(process.env.BLENTO_BACKUP_LIMIT) : Infinity; +const CONCURRENCY = Number(process.env.BLENTO_BACKUP_CONCURRENCY ?? 12); + +/** blento-owned records we preserve (content + legacy/new roots + pronouns). */ +const COLLECTIONS = [ + 'app.blento.node', + 'app.blento.card', + 'app.blento.section', + 'app.blento.page', + 'site.standard.publication', + 'app.nearhorizon.actor.pronouns' +]; +/** collections used to discover which repos are blento users at all. */ +const DISCOVERY_COLLECTIONS = ['app.blento.card', 'app.blento.node']; + +interface JsonObject { + [k: string]: unknown; +} + +async function getJson(url: string | URL): Promise { + const res = await fetch(url); + if (!res.ok) throw new Error(`${res.status} ${res.statusText} for ${url}`); + return res.json(); +} + +/** All DIDs that have at least one record in `collection`, via the relay. */ +async function listReposByCollection(collection: string): Promise { + const dids: string[] = []; + let cursor: string | undefined; + do { + const url = new URL(`${RELAY}/xrpc/com.atproto.sync.listReposByCollection`); + url.searchParams.set('collection', collection); + url.searchParams.set('limit', '500'); + if (cursor) url.searchParams.set('cursor', cursor); + let data: { repos?: { did: string }[]; cursor?: string }; + try { + data = (await getJson(url)) as typeof data; + } catch (e) { + console.warn(` ! listReposByCollection(${collection}) failed: ${(e as Error).message}`); + break; + } + for (const r of data.repos ?? []) dids.push(r.did); + cursor = data.cursor; + } while (cursor); + return dids; +} + +/** Resolve a DID's PDS service endpoint (PLC + did:web). */ +async function resolvePds(did: string): Promise { + try { + const doc = did.startsWith('did:web:') + ? ((await getJson( + `https://${did.slice('did:web:'.length)}/.well-known/did.json` + )) as JsonObject) + : ((await getJson(`https://plc.directory/${did}`)) as JsonObject); + const services = (doc.service as { id: string; serviceEndpoint: string }[]) ?? []; + return services.find((s) => s.id === '#atproto_pds')?.serviceEndpoint ?? null; + } catch { + return null; + } +} + +async function listRecords( + pds: string, + did: string, + collection: string +): Promise<{ rkey: string; value: unknown }[]> { + const out: { rkey: string; value: unknown }[] = []; + let cursor: string | undefined; + do { + const url = new URL(`${pds}/xrpc/com.atproto.repo.listRecords`); + url.searchParams.set('repo', did); + url.searchParams.set('collection', collection); + url.searchParams.set('limit', '100'); + if (cursor) url.searchParams.set('cursor', cursor); + let data: { records?: { uri: string; value: unknown }[]; cursor?: string }; + try { + data = (await getJson(url)) as typeof data; + } catch { + break; // collection absent / PDS hiccup — treat as no records + } + for (const r of data.records ?? []) out.push({ rkey: r.uri.split('/').pop()!, value: r.value }); + cursor = data.cursor; + } while (cursor); + return out; +} + +/** Recursively collect every blob CID referenced anywhere in a record. */ +function collectBlobCids(value: unknown, acc: Set): void { + if (!value || typeof value !== 'object') return; + const obj = value as JsonObject; + if (obj.$type === 'blob') { + const ref = obj.ref as { $link?: string } | string | undefined; + const cid = typeof ref === 'string' ? ref : ref?.$link; + if (typeof cid === 'string') acc.add(cid); + return; + } + for (const v of Object.values(obj)) { + if (Array.isArray(v)) v.forEach((x) => collectBlobCids(x, acc)); + else collectBlobCids(v, acc); + } +} + +async function downloadBlob( + pds: string, + did: string, + cid: string, + destDir: string +): Promise { + const url = new URL(`${pds}/xrpc/com.atproto.sync.getBlob`); + url.searchParams.set('did', did); + url.searchParams.set('cid', cid); + const res = await fetch(url); + if (!res.ok) { + console.warn(` ! blob ${cid}: ${res.status}`); + return false; + } + await writeFile(join(destDir, cid), Buffer.from(await res.arrayBuffer())); + return true; +} + +async function backupRepo(did: string, root: string): Promise<{ records: number; blobs: number }> { + const pds = await resolvePds(did); + if (!pds) { + console.warn(` ! ${did}: no PDS endpoint, skipping`); + return { records: 0, blobs: 0 }; + } + const didDir = join(root, did); + const blobCids = new Set(); + let records = 0; + + for (const collection of COLLECTIONS) { + const recs = await listRecords(pds, did, collection); + if (recs.length === 0) continue; + const colDir = join(didDir, collection); + await mkdir(colDir, { recursive: true }); + for (const rec of recs) { + await writeFile(join(colDir, `${rec.rkey}.json`), JSON.stringify(rec.value, null, 2)); + collectBlobCids(rec.value, blobCids); + records++; + } + } + + let blobs = 0; + if (blobCids.size > 0) { + const blobDir = join(didDir, '_blobs'); + await mkdir(blobDir, { recursive: true }); + for (const cid of blobCids) if (await downloadBlob(pds, did, cid, blobDir)) blobs++; + } + return { records, blobs }; +} + +// ---- main ---- +const stamp = new Date().toISOString().replace(/[:.]/g, '-'); +const root = join(OUT_ROOT, stamp); +await mkdir(root, { recursive: true }); +console.log(`Backup → ${root}`); +console.log(`Discovering repos via ${RELAY} ...`); + +const didSet = new Set(); +for (const c of DISCOVERY_COLLECTIONS) { + const dids = await listReposByCollection(c); + dids.forEach((d) => didSet.add(d)); + console.log(` ${c}: ${dids.length} repos`); +} +let dids = [...didSet]; +if (dids.length > LIMIT) dids = dids.slice(0, LIMIT); +console.log( + `${didSet.size} unique repos${Number.isFinite(LIMIT) ? ` (limited to ${dids.length})` : ''}\n` +); + +let okRepos = 0; +let failRepos = 0; +let totalRecords = 0; +let totalBlobs = 0; +let nextIndex = 0; +let completed = 0; + +// Repos run in parallel (almost all time is network wait); each repo's own collections + blobs stay +// sequential to be gentle on a single PDS. Workers pull from a shared cursor until the list is drained. +async function worker(): Promise { + for (;;) { + const i = nextIndex++; + if (i >= dids.length) return; + const did = dids[i]; + try { + const { records, blobs } = await backupRepo(did, root); + totalRecords += records; + totalBlobs += blobs; + okRepos++; + console.log(`[${++completed}/${dids.length}] ${did} — ${records} records, ${blobs} blobs`); + } catch (e) { + failRepos++; + console.warn(`[${++completed}/${dids.length}] ${did} — FAILED: ${(e as Error).message}`); + } + } +} + +console.log(`Backing up with concurrency ${CONCURRENCY} ...`); +await Promise.all(Array.from({ length: Math.min(CONCURRENCY, dids.length) }, () => worker())); + +console.log( + `\nDone: ${okRepos} repos ok, ${failRepos} failed, ${totalRecords} records, ${totalBlobs} blobs` +); +console.log(`→ ${root}`); diff --git a/packages/backup/package.json b/packages/backup/package.json new file mode 100644 index 00000000..5fd6a9cc --- /dev/null +++ b/packages/backup/package.json @@ -0,0 +1,13 @@ +{ + "name": "@blento/backup", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "tsx backup.ts" + }, + "devDependencies": { + "@types/node": "^25.9.1", + "tsx": "^4.21.0" + } +} diff --git a/packages/diff-harness/README.md b/packages/diff-harness/README.md new file mode 100644 index 00000000..f62d11e0 --- /dev/null +++ b/packages/diff-harness/README.md @@ -0,0 +1,66 @@ +# Differential render harness + +**The keystone de-risk for the rewrite.** Diff the new view output against the *current live site* +across all ~1000 real sites. Production is the oracle. See `../../../../blento-v2-plan.md` §9. + +Covers the **view side only**. Editor behavior (drag/collision/undo/save), OAuth, and write/migration +paths are tested separately via Playwright (`apps/web/tests`) + Vitest. + +## Pipeline (to build in Phase 1 — before serious generation) + +1. **`snapshot.ts`** — enumerate live site identifiers (DIDs/handles) and fetch rendered HTML from + `DIFF_LIVE_ORIGIN` for each. Save to `diff-snapshots/.html` (+ the raw upstream data + responses per site, so renders are reproducible). +2. **`freeze.ts`** — record/replay the upstream data (bluesky posts, events, external APIs) so the + diff compares *rendering*, not live data drift. Feed identical frozen data into old + new renders. +3. **`diff.ts`** — render the same data through the new app and compare: + - **Primary gate: structural/semantic DOM diff** — normalize then compare elements, text, hrefs, + img srcs, order, ARIA. Robust to class changes (we drop Tailwind) and font/AA noise. + - **Optional: pixel diff** behind a "compatibility theme" — only if we decide the view must be + pixel-identical. Default: structural-first, accept intended visual drift. +4. **`report.ts`** — per-site pass/fail + a divergence summary. This is the objective gate for the + one-night generation and the permanent regression net. + +## Open decision (plan §12) + +Structural-faithful (DOM diff is the gate — recommended) vs visually-identical (build the compat +theme + pixel diff). Given the de-Tailwind, recommend structural-first. + +## Modules + +- **`normalize.ts`** ✓ — HTML → canonical structural tree. Drops class/style/id/whitespace/comments, + keeps semantic attrs (href/src/alt/role/aria/…), treats `` as an opaque leaf. This is what + makes the diff robust to the de-Tailwind class churn. +- **`diff.ts`** ✓ — position-aligned structural diff → `Divergence[]` (tag/text/attr/missing/extra). + ⚠️ Known limitation (found by running it): it stops descending at a tag mismatch, so when the two + DOMs are shaped differently (v2's page shell vs v1's) it UNDER-reports — a top-level wrapper + mismatch masks everything inside. Use `content.ts` while structures differ; use this once they converge. +- **`content.ts`** ✓ — structure-INDEPENDENT fidelity diff: compares the *set* of links/images/text + anchors, answering "what content did the new render drop or add?" regardless of markup. This is the + primary gate during the rewrite. CLI: `… content `. +- **`sites.ts`** ✓ — enumerate live sites via the production contrail XRPC + (`app.blento.card.listRecords` cross-actor, recency-sorted, paginated). `MAX_SITES` env caps it. +- **`snapshot.ts`** ✓ — fetch each site's HTML from `DIFF_LIVE_ORIGIN`, save `diff-snapshots/.html` + + a `manifest.json`. **Run after the open v1 PRs merge** so new cards are in the oracle. +- **`compare.ts`** ✓ — single-file CLI: `pnpm --filter @blento/diff-harness compare `. +- **`report.ts`** ✓ — batch CLI: `… report ` → per-site divergence summary, + worst-first; exit 1 if any site diverges or is missing. The pass/fail gate for the view side. +- **`normalize.test.ts`** ✓ — proves class/style/whitespace/svg-internal changes are ignored while + href/text/structure changes are flagged. `pnpm --filter @blento/diff-harness test` (4/4). +- **`freeze.ts`** ⧗ — stub. Record/replay upstream data so the diff compares rendering, not bluesky/ + event drift. Design is decided; implementation waits on the v2 data loaders (Phase 5/7). + +## Workflow + +``` +pnpm --filter @blento/diff-harness sites # enumerate (MAX_SITES=N to cap) +pnpm --filter @blento/diff-harness snapshot # capture live oracle -> diff-snapshots/ +# … render v2 for the same sites into another dir (Phase 5) … +pnpm --filter @blento/diff-harness report diff-snapshots +``` + +## Status + +Real and validated end-to-end against production (snapshot→normalize→diff): a self-compare of 5 live +sites reports 0 divergences, and an injected change is caught. Remaining: `freeze` (needs v2 loaders) +and wiring the v2 render output as the `report` "new" side. `diff-snapshots/` is git-ignored. diff --git a/packages/diff-harness/compare.ts b/packages/diff-harness/compare.ts new file mode 100644 index 00000000..7776e516 --- /dev/null +++ b/packages/diff-harness/compare.ts @@ -0,0 +1,36 @@ +/** + * CLI: structurally diff two HTML files (live snapshot vs new render). + * node --experimental-strip-types compare.ts + * Exit code 0 = structurally identical; 1 = divergences (printed). + */ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { normalizeHtml } from './normalize.ts'; +import { diffStruct } from './diff.ts'; + +// Resolve args against where the command was invoked (pnpm runs scripts in the package dir). +const base = process.env.INIT_CWD ?? process.cwd(); +const [liveArg, newArg] = process.argv.slice(2); +if (!liveArg || !newArg) { + console.error('usage: compare '); + process.exit(2); +} +const liveFile = resolve(base, liveArg); +const newFile = resolve(base, newArg); + +const divergences = diffStruct( + normalizeHtml(readFileSync(liveFile, 'utf8')), + normalizeHtml(readFileSync(newFile, 'utf8')) +); + +if (divergences.length === 0) { + console.log('✓ structurally identical'); + process.exit(0); +} + +console.log(`✗ ${divergences.length} divergence(s):\n`); +for (const d of divergences.slice(0, 200)) { + console.log(` [${d.kind}] ${d.path}\n ${d.detail}`); +} +if (divergences.length > 200) console.log(` … and ${divergences.length - 200} more`); +process.exit(1); diff --git a/packages/diff-harness/content.test.ts b/packages/diff-harness/content.test.ts new file mode 100644 index 00000000..c8859b54 --- /dev/null +++ b/packages/diff-harness/content.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { contentDiff } from './content.ts'; + +describe('content-anchor diff (structure-independent)', () => { + it('ignores wrapper structure when content is the same', () => { + const live = ``; + const next = `
hi
`; + const d = contentDiff(live, next); + expect(d.missingLinks).toEqual([]); + expect(d.missingImages).toEqual([]); + }); + + it('flags links and images the new render dropped', () => { + const live = `kd`; + const next = `k`; + const d = contentDiff(live, next); + expect(d.missingLinks).toEqual(['/dropped']); + expect(d.missingImages).toEqual(['/i.png']); + expect(d.extraLinks).toEqual([]); + }); +}); diff --git a/packages/diff-harness/content.ts b/packages/diff-harness/content.ts new file mode 100644 index 00000000..46b82149 --- /dev/null +++ b/packages/diff-harness/content.ts @@ -0,0 +1,96 @@ +/** + * Content-anchor diff — a structure-INDEPENDENT fidelity check. + * + * The position-aligned structural diff (diff.ts) under-reports when the two DOMs are shaped + * differently: it stops descending at a tag mismatch, so a different page shell (v2's `
` vs + * v1's wrapper `
`) masks everything inside. This instead compares the *set of semantic + * anchors* — links (hrefs), images (srcs), and text runs — answering the real rewrite question: + * "what content did the new render drop or add?" regardless of markup. Use this as the primary + * fidelity gate while v2's structure differs from v1; use diff.ts once structures converge. + */ +import { readdir, readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { normalizeHtml, type StructNode } from './normalize.ts'; + +export interface ContentAnchors { + links: string[]; + images: string[]; + text: string[]; +} + +export function contentAnchors(html: string): ContentAnchors { + const links = new Set(); + const images = new Set(); + const text: string[] = []; + const walk = (nodes: StructNode[]) => { + for (const n of nodes) { + if (n.kind === 'text') { + if (n.text.length > 1) text.push(n.text.toLowerCase()); + continue; + } + if (n.tag === 'a' && n.attrs.href) links.add(n.attrs.href); + if (n.tag === 'img' && n.attrs.src) images.add(n.attrs.src); + walk(n.children); + } + }; + walk(normalizeHtml(html)); + return { links: [...links], images: [...images], text }; +} + +const missing = (a: string[], b: string[]) => { + const s = new Set(b); + return a.filter((x) => !s.has(x)); +}; + +export interface ContentDiff { + missingLinks: string[]; + extraLinks: string[]; + missingImages: string[]; + extraImages: string[]; + missingTextCount: number; + extraTextCount: number; +} + +export function contentDiff(liveHtml: string, newHtml: string): ContentDiff { + const a = contentAnchors(liveHtml); + const b = contentAnchors(newHtml); + return { + missingLinks: missing(a.links, b.links), + extraLinks: missing(b.links, a.links), + missingImages: missing(a.images, b.images), + extraImages: missing(b.images, a.images), + missingTextCount: missing(a.text, b.text).length, + extraTextCount: missing(b.text, a.text).length + }; +} + +// CLI: `content ` → per-site + total missing/extra links & images. +if (import.meta.url === `file://${process.argv[1]}`) { + const base = process.env.INIT_CWD ?? process.cwd(); + const [liveArg, newArg] = process.argv.slice(2); + if (!liveArg || !newArg) { + console.error('usage: content '); + process.exit(2); + } + const liveDir = resolve(base, liveArg); + const newDir = resolve(base, newArg); + const files = (await readdir(liveDir)).filter((f) => f.endsWith('.html')); + let totLink = 0; + let totImg = 0; + for (const file of files) { + let newHtml: string; + try { + newHtml = await readFile(`${newDir}/${file}`, 'utf8'); + } catch { + continue; + } + const d = contentDiff(await readFile(`${liveDir}/${file}`, 'utf8'), newHtml); + totLink += d.missingLinks.length; + totImg += d.missingImages.length; + console.log( + ` ${file.replace(/\.html$/, '')}: missing ${d.missingLinks.length} links, ${d.missingImages.length} images (text runs −${d.missingTextCount})` + ); + for (const l of d.missingLinks.slice(0, 6)) console.log(` − ${l}`); + } + console.log(`\ntotal missing across sites: ${totLink} links, ${totImg} images`); +} diff --git a/packages/diff-harness/diff.ts b/packages/diff-harness/diff.ts new file mode 100644 index 00000000..da45f6ab --- /dev/null +++ b/packages/diff-harness/diff.ts @@ -0,0 +1,69 @@ +/** + * Structural diff — compares two normalized trees by position and reports divergences with a path. + * Position-aligned (not LCS): good enough to flag "the structure/content changed" for the rewrite + * gate. A future improvement is LCS alignment to reduce cascade noise after an insert/delete. + */ +import type { StructNode } from './normalize.ts'; + +export type DivergenceKind = 'tag' | 'text' | 'attr' | 'missing' | 'extra' | 'kind'; + +export interface Divergence { + path: string; + kind: DivergenceKind; + detail: string; +} + +export function diffStruct(a: StructNode[], b: StructNode[], path = ''): Divergence[] { + const out: Divergence[] = []; + const max = Math.max(a.length, b.length); + for (let i = 0; i < max; i++) { + const left = a[i]; + const right = b[i]; + const here = `${path}/${i}`; + + if (left && !right) { + out.push({ path: here, kind: 'missing', detail: describe(left) + ' present in live, absent in new' }); + continue; + } + if (!left && right) { + out.push({ path: here, kind: 'extra', detail: describe(right) + ' present in new, absent in live' }); + continue; + } + if (!left || !right) continue; + + if (left.kind !== right.kind) { + out.push({ path: here, kind: 'kind', detail: `${left.kind} vs ${right.kind}` }); + continue; + } + + if (left.kind === 'text' && right.kind === 'text') { + if (left.text !== right.text) { + out.push({ path: here, kind: 'text', detail: `"${left.text}" vs "${right.text}"` }); + } + continue; + } + + if (left.kind === 'el' && right.kind === 'el') { + const p = `${here}/${left.tag}`; + if (left.tag !== right.tag) { + out.push({ path: p, kind: 'tag', detail: `<${left.tag}> vs <${right.tag}>` }); + continue; // different element — don't recurse into mismatched subtrees + } + for (const k of new Set([...Object.keys(left.attrs), ...Object.keys(right.attrs)])) { + if (left.attrs[k] !== right.attrs[k]) { + out.push({ + path: p, + kind: 'attr', + detail: `${k}: ${JSON.stringify(left.attrs[k])} vs ${JSON.stringify(right.attrs[k])}` + }); + } + } + out.push(...diffStruct(left.children, right.children, p)); + } + } + return out; +} + +function describe(n: StructNode): string { + return n.kind === 'text' ? `text "${n.text.slice(0, 40)}"` : `<${n.tag}>`; +} diff --git a/packages/diff-harness/freeze.ts b/packages/diff-harness/freeze.ts new file mode 100644 index 00000000..a7c1a892 --- /dev/null +++ b/packages/diff-harness/freeze.ts @@ -0,0 +1,28 @@ +/** + * Data freeze — record/replay the upstream data a render depends on, so the v2 render and the live + * snapshot are compared on identical inputs (not live bluesky/event drift). Plan §9. + * + * STUB — depends on the v2 data loaders, which don't exist yet (Phase 5/7). Decided design: + * + * 1. At snapshot time, capture not just HTML but each site's upstream loader inputs — i.e. record + * the responses the card `source`/`loadDataServer` calls would make (bluesky posts, events, + * lastfm, github, …), keyed by request. Save alongside the HTML as `.data.json`. + * 2. When rendering the v2 app for that site, route its loaders' outbound fetches through a + * replay layer seeded from `.data.json` (cache hit → recorded response; miss → real fetch + * + warn). This makes the v2 render deterministic against the captured instant. + * 3. report.ts then diffs HTML rendered from the *same* data → divergences are real rendering + * changes, not data drift. + * + * Until the v2 loaders exist, the cheaper interim is to snapshot + render close in time and rely on + * the structural diff ignoring volatile leaf text. Implement true freeze when loaders land. + */ + +export interface FrozenData { + did: string; + /** request key -> recorded JSON response */ + responses: Record; +} + +export function createReplay(_frozen: FrozenData): never { + throw new Error('freeze: implement once v2 data loaders exist (Phase 5/7) — see plan §9'); +} diff --git a/packages/diff-harness/frequency.ts b/packages/diff-harness/frequency.ts new file mode 100644 index 00000000..ec74fdb4 --- /dev/null +++ b/packages/diff-harness/frequency.ts @@ -0,0 +1,53 @@ +/** + * Card-type frequency across the network — drives the port priority. Paginates the cross-actor + * `app.blento.card.listRecords` and tallies `cardType`, so we port the ~10 types that cover most + * content rather than all 67. Also reports how many sites use each type. + */ +const origin = process.env.DIFF_LIVE_ORIGIN ?? 'https://blento.app'; +const maxRequests = process.env.MAX_REQUESTS ? Number(process.env.MAX_REQUESTS) : 80; + +const cardCount = new Map(); +const siteSet = new Map>(); // type -> set of dids +let cursor: string | undefined; +let total = 0; + +for (let req = 0; req < maxRequests; req++) { + const url = new URL(`${origin}/xrpc/app.blento.card.listRecords`); + url.searchParams.set('limit', '200'); + url.searchParams.set('sort', 'time_us'); + url.searchParams.set('order', 'desc'); + if (cursor) url.searchParams.set('cursor', cursor); + + const res = await fetch(url, { headers: { 'user-agent': 'blento-diff-harness' } }); + if (!res.ok) break; + const data = (await res.json()) as { + records: Array<{ did: string; value: { cardType?: string } }>; + cursor?: string; + }; + + for (const r of data.records) { + const t = r.value?.cardType ?? 'unknown'; + cardCount.set(t, (cardCount.get(t) ?? 0) + 1); + let s = siteSet.get(t); + if (!s) { + s = new Set(); + siteSet.set(t, s); + } + s.add(r.did); + total++; + } + cursor = data.cursor; + if (!cursor || data.records.length === 0) break; +} + +const ported = new Set(['grid', 'link', 'text', 'image']); +const ranked = [...cardCount.entries()].sort((a, b) => b[1] - a[1]); + +console.log(`sampled ${total} cards across ${new Set([...siteSet.values()].flatMap((s) => [...s])).size} sites\n`); +console.log('rank cards sites type'); +ranked.forEach(([type, n], i) => { + const mark = ported.has(type) ? '✓' : ' '; + console.log( + `${String(i + 1).padStart(3)} ${String(n).padStart(5)} ${String(siteSet.get(type)!.size).padStart(5)} ${mark} ${type}` + ); +}); diff --git a/packages/diff-harness/measure.ts b/packages/diff-harness/measure.ts new file mode 100644 index 00000000..59b854ea --- /dev/null +++ b/packages/diff-harness/measure.ts @@ -0,0 +1,47 @@ +/** + * Measure the live page's actual computed layout — so we match v1's real numbers instead of + * guessing from screenshots. Dumps rects + font/spacing for the avatar, name, description, and the + * first grid cards (v1 uses the `.grid-card` class in the read-only view too). + */ +import { chromium } from 'playwright'; + +const url = process.argv[2] ?? 'https://blento.app/hoopinformatics.bsky.social'; +const browser = await chromium.launch(); +try { + const page = await browser.newPage({ + viewport: { width: 1280, height: 1800 }, + colorScheme: 'dark' + }); + await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); + // Passed as a string so tsx/esbuild doesn't inject helpers (__name) into the browser context. + const data = await page.evaluate(`(function () { + function m(el) { + if (!el) return null; + var r = el.getBoundingClientRect(); + var s = getComputedStyle(el); + return { + rect: { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) }, + font: s.fontSize + ' / ' + s.lineHeight + ' w' + s.fontWeight, + padding: s.padding, + gap: s.gap, + borderRadius: s.borderRadius + }; + } + var name = null, max = 0; + document.querySelectorAll('h1, div, span, p').forEach(function (el) { + if (!el.textContent || !el.textContent.trim() || el.children.length > 1) return; + var size = parseFloat(getComputedStyle(el).fontSize); + if (size > max) { max = size; name = el; } + }); + var cards = Array.prototype.slice.call(document.querySelectorAll('.grid-card'), 0, 4).map(m); + return { + avatar: m(document.querySelector('img')), + name: m(name), + description: m(document.querySelector('[class*="prose"]')), + cards: cards + }; + })()`); + console.log(JSON.stringify(data, null, 2)); +} finally { + await browser.close(); +} diff --git a/packages/diff-harness/normalize.test.ts b/packages/diff-harness/normalize.test.ts new file mode 100644 index 00000000..b6d209f3 --- /dev/null +++ b/packages/diff-harness/normalize.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect } from 'vitest'; +import { normalizeHtml } from './normalize.ts'; +import { diffStruct } from './diff.ts'; + +describe('structural diff', () => { + it('ignores class/style/whitespace changes (the de-Tailwind case)', () => { + const live = ``; + const next = ``; + expect(diffStruct(normalizeHtml(live), normalizeHtml(next))).toEqual([]); + }); + + it('flags a changed href (real content/structure change)', () => { + const live = `Hi`; + const next = `Hi`; + const d = diffStruct(normalizeHtml(live), normalizeHtml(next)); + expect(d).toHaveLength(1); + expect(d[0].kind).toBe('attr'); + }); + + it('flags changed text and missing nodes', () => { + const live = `
  • a
  • b
`; + const next = `
  • a
`; + const d = diffStruct(normalizeHtml(live), normalizeHtml(next)); + expect(d.some((x) => x.kind === 'missing')).toBe(true); + }); + + it('treats svg as an opaque leaf (icon internals are noise)', () => { + const live = ``; + const next = ``; + // same svg attrs, different internal path data → children dropped → no divergence + expect(diffStruct(normalizeHtml(live), normalizeHtml(next))).toEqual([]); + }); +}); diff --git a/packages/diff-harness/normalize.ts b/packages/diff-harness/normalize.ts new file mode 100644 index 00000000..6396676e --- /dev/null +++ b/packages/diff-harness/normalize.ts @@ -0,0 +1,102 @@ +/** + * Structural normalization — the core of the structural-faithful diff (plan §9). + * + * Reduces an HTML document to a canonical tree of meaning: tag names, a small set of semantic + * attributes (href/src/alt/role/aria/…), and normalized text. It deliberately DROPS classes, + * inline styles, ids, whitespace, comments, and presentational noise — so the diff is robust to + * the styling change (we drop Tailwind) and flags only changes that alter content or structure. + */ +import { parse, type HTMLElement, type Node as PNode } from 'node-html-parser'; + +export interface StructEl { + kind: 'el'; + tag: string; + attrs: Record; + children: StructNode[]; +} +export interface StructText { + kind: 'text'; + text: string; +} +export type StructNode = StructEl | StructText; + +/** Tags whose presence and contents are irrelevant to rendered structure. */ +const DROP_TAGS = new Set(['script', 'style', 'noscript', 'template', 'link', 'meta', 'head']); + +/** The only attributes that carry structural/content meaning. Everything else is dropped. */ +const KEEP_ATTRS = new Set([ + 'href', + 'src', + 'srcset', + 'alt', + 'role', + 'type', + 'name', + 'value', + 'for', + 'rel', + 'target', + 'controls', + 'poster', + 'loading', + 'title', + 'lang', + 'aria-label', + 'aria-hidden', + 'aria-current', + 'aria-expanded' +]); + +/** Tags kept as opaque leaves — their internal geometry is presentation noise (icon path data). */ +const OPAQUE_TAGS = new Set(['svg']); + +export interface NormalizeOptions { + keepAttrs?: Set; + dropTags?: Set; + opaqueTags?: Set; +} + +const ws = (s: string) => s.replace(/\s+/g, ' ').trim(); + +export function normalizeHtml(html: string, opts: NormalizeOptions = {}): StructNode[] { + const keep = opts.keepAttrs ?? KEEP_ATTRS; + const drop = opts.dropTags ?? DROP_TAGS; + const opaque = opts.opaqueTags ?? OPAQUE_TAGS; + const root = parse(html, { comment: false, blockTextElements: { script: false, style: false } }); + return normChildren(root.childNodes, keep, drop, opaque); +} + +function normChildren( + nodes: PNode[], + keep: Set, + drop: Set, + opaque: Set +): StructNode[] { + const out: StructNode[] = []; + for (const node of nodes) { + // nodeType: 1 = element, 3 = text (per node-html-parser) + if (node.nodeType === 3) { + const text = ws(node.text ?? ''); + if (text) out.push({ kind: 'text', text }); + continue; + } + if (node.nodeType !== 1) continue; + const el = node as HTMLElement; + const tag = (el.rawTagName ?? '').toLowerCase(); + if (!tag || drop.has(tag)) continue; + + const attrs: Record = {}; + for (const [k, v] of Object.entries(el.attributes ?? {})) { + const key = k.toLowerCase(); + if (keep.has(key)) attrs[key] = ws(String(v)); + } + + out.push({ + kind: 'el', + tag, + attrs, + children: opaque.has(tag) ? [] : normChildren(el.childNodes, keep, drop, opaque) + }); + } + return out; +} diff --git a/packages/diff-harness/package.json b/packages/diff-harness/package.json new file mode 100644 index 00000000..eb602e8a --- /dev/null +++ b/packages/diff-harness/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blento/diff-harness", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "vitest run", + "check": "tsc --noEmit", + "sites": "tsx sites.ts", + "snapshot": "tsx snapshot.ts", + "compare": "tsx compare.ts", + "report": "tsx report.ts", + "content": "tsx content.ts", + "frequency": "tsx frequency.ts", + "screenshot": "tsx screenshot.ts", + "pixel": "tsx pixel.ts", + "measure": "tsx measure.ts" + }, + "dependencies": { + "node-html-parser": "^7.0.1", + "pixelmatch": "^6.0.0", + "playwright": "^1.50.0", + "pngjs": "^7.0.0" + }, + "devDependencies": { + "@types/pngjs": "^6.0.5", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vitest": "^4.1.4" + } +} diff --git a/packages/diff-harness/pixel.ts b/packages/diff-harness/pixel.ts new file mode 100644 index 00000000..96bc866d --- /dev/null +++ b/packages/diff-harness/pixel.ts @@ -0,0 +1,54 @@ +/** + * Pixel diff between two screenshots → a diff image + mismatch ratio. The visual fidelity gate + * (complement to the content diff). Crops both to common dimensions since v1/v2 page heights differ. + */ +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { PNG } from 'pngjs'; +import pixelmatch from 'pixelmatch'; + +function cropData(src: PNG, w: number, h: number): Buffer { + if (src.width === w && src.height === h) return src.data; + const out = Buffer.alloc(w * h * 4); + for (let y = 0; y < h; y++) { + src.data.copy(out, y * w * 4, y * src.width * 4, y * src.width * 4 + w * 4); + } + return out; +} + +export interface PixelResult { + ratio: number; + diffPixels: number; + width: number; + height: number; +} + +export function pixelDiff(aPath: string, bPath: string, diffOut: string): PixelResult { + const a = PNG.sync.read(readFileSync(aPath)); + const b = PNG.sync.read(readFileSync(bPath)); + const width = Math.min(a.width, b.width); + const height = Math.min(a.height, b.height); + const diff = new PNG({ width, height }); + const diffPixels = pixelmatch( + cropData(a, width, height), + cropData(b, width, height), + diff.data, + width, + height, + { threshold: 0.15 } + ); + writeFileSync(diffOut, PNG.sync.write(diff)); + return { ratio: diffPixels / (width * height), diffPixels, width, height }; +} + +// CLI: `pixel ` +if (import.meta.url === `file://${process.argv[1]}`) { + const base = process.env.INIT_CWD ?? process.cwd(); + const [a, b, out] = process.argv.slice(2); + if (!a || !b || !out) { + console.error('usage: pixel '); + process.exit(2); + } + const r = pixelDiff(resolve(base, a), resolve(base, b), resolve(base, out)); + console.log(`mismatch: ${(r.ratio * 100).toFixed(1)}% (${r.diffPixels}px of ${r.width}×${r.height})`); +} diff --git a/packages/diff-harness/report.ts b/packages/diff-harness/report.ts new file mode 100644 index 00000000..8038581e --- /dev/null +++ b/packages/diff-harness/report.ts @@ -0,0 +1,71 @@ +/** + * Batch structural compare — live snapshots vs new (v2) renders. The pass/fail gate for the + * rewrite's view side (plan §9). Operates on two directories of `.html` files; produces a + * per-site divergence summary, worst-offenders first. + */ +import { readdir, readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { normalizeHtml } from './normalize.ts'; +import { diffStruct, type Divergence } from './diff.ts'; + +export interface SiteReport { + id: string; + divergences: number; + sample: Divergence[]; +} + +export interface BatchReport { + total: number; + identical: number; + changed: number; + missingFromNew: string[]; + sites: SiteReport[]; +} + +export async function batchCompare(liveDir: string, newDir: string): Promise { + const liveFiles = (await readdir(liveDir)).filter((f) => f.endsWith('.html')); + const sites: SiteReport[] = []; + const missingFromNew: string[] = []; + + for (const file of liveFiles) { + const id = file.replace(/\.html$/, ''); + let newHtml: string; + try { + newHtml = await readFile(`${newDir}/${file}`, 'utf8'); + } catch { + missingFromNew.push(id); + continue; + } + const liveHtml = await readFile(`${liveDir}/${file}`, 'utf8'); + const d = diffStruct(normalizeHtml(liveHtml), normalizeHtml(newHtml)); + sites.push({ id, divergences: d.length, sample: d.slice(0, 5) }); + } + + sites.sort((a, b) => b.divergences - a.divergences); + return { + total: sites.length, + identical: sites.filter((s) => s.divergences === 0).length, + changed: sites.filter((s) => s.divergences > 0).length, + missingFromNew, + sites + }; +} + +// CLI: `tsx report.ts ` → summary + exit 1 if any site diverges or is missing. +if (import.meta.url === `file://${process.argv[1]}`) { + const base = process.env.INIT_CWD ?? process.cwd(); + const [liveArg, newArg] = process.argv.slice(2); + if (!liveArg || !newArg) { + console.error('usage: report '); + process.exit(2); + } + const r = await batchCompare(resolve(base, liveArg), resolve(base, newArg)); + console.log( + `sites: ${r.total} | identical: ${r.identical} | changed: ${r.changed} | missing-from-new: ${r.missingFromNew.length}` + ); + for (const s of r.sites.filter((x) => x.divergences > 0).slice(0, 25)) { + console.log(`\n ${s.id}: ${s.divergences} divergence(s)`); + for (const d of s.sample) console.log(` [${d.kind}] ${d.path} — ${d.detail}`); + } + process.exit(r.changed === 0 && r.missingFromNew.length === 0 ? 0 : 1); +} diff --git a/packages/diff-harness/screenshot.ts b/packages/diff-harness/screenshot.ts new file mode 100644 index 00000000..394d0e3a --- /dev/null +++ b/packages/diff-harness/screenshot.ts @@ -0,0 +1,40 @@ +/** + * Capture a full-page screenshot of a URL (headless Chromium). Used to make visual fidelity + * measurable: screenshot v1 (live) and v2 (dev) for the same site, then pixel-diff them. + */ +import { resolve } from 'node:path'; +import { chromium } from 'playwright'; + +export interface ShotOptions { + width?: number; + height?: number; + fullPage?: boolean; +} + +export async function capture(url: string, outPath: string, opts: ShotOptions = {}): Promise { + const browser = await chromium.launch(); + try { + const page = await browser.newPage({ + viewport: { width: opts.width ?? 1280, height: opts.height ?? 1800 }, + colorScheme: 'dark', + deviceScaleFactor: 1 + }); + await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 }); + await page.waitForTimeout(600); + await page.screenshot({ path: outPath, fullPage: opts.fullPage ?? true }); + } finally { + await browser.close(); + } +} + +// CLI: `screenshot ` +if (import.meta.url === `file://${process.argv[1]}`) { + const base = process.env.INIT_CWD ?? process.cwd(); + const [url, out] = process.argv.slice(2); + if (!url || !out) { + console.error('usage: screenshot '); + process.exit(2); + } + await capture(url, resolve(base, out)); + console.error(`saved ${out}`); +} diff --git a/packages/diff-harness/sites.ts b/packages/diff-harness/sites.ts new file mode 100644 index 00000000..29f7202a --- /dev/null +++ b/packages/diff-harness/sites.ts @@ -0,0 +1,78 @@ +/** + * Enumerate live blento sites — the diff oracle's subject list (plan §9). + * + * Uses the same source as v1's "Updated Blentos" card: the production contrail XRPC + * `app.blento.card.listRecords` cross-actor (no `actor` param), recency-sorted, paginated by + * `cursor`. We dedupe DIDs and pull handles from the `profiles` side-channel. + */ +export interface SiteRef { + did: string; + handle?: string; +} + +interface ListResp { + records: Array<{ did: string }>; + cursor?: string; + profiles?: Array<{ did: string; handle?: string }>; +} + +export interface ListSitesOptions { + origin?: string; + /** Cap on unique sites returned (default: all). */ + maxSites?: number; + /** Records per request (default 200). */ + pageSize?: number; + /** Safety cap on pagination requests (default 500). */ + maxRequests?: number; +} + +const isJunk = (s: string) => s.endsWith('.pds.rip') || s === 'handle.invalid'; + +export async function listSites(opts: ListSitesOptions = {}): Promise { + const origin = opts.origin ?? process.env.DIFF_LIVE_ORIGIN ?? 'https://blento.app'; + const pageSize = opts.pageSize ?? 200; + const maxSites = opts.maxSites ?? Infinity; + const maxRequests = opts.maxRequests ?? 500; + + const handles = new Map(); + const order: string[] = []; + const seen = new Set(); + let cursor: string | undefined; + + for (let req = 0; req < maxRequests; req++) { + const url = new URL(`${origin}/xrpc/app.blento.card.listRecords`); + url.searchParams.set('limit', String(pageSize)); + url.searchParams.set('profiles', 'true'); + url.searchParams.set('sort', 'time_us'); + url.searchParams.set('order', 'desc'); + if (cursor) url.searchParams.set('cursor', cursor); + + const res = await fetch(url, { headers: { 'user-agent': 'blento-diff-harness' } }); + if (!res.ok) throw new Error(`listRecords ${res.status} on request ${req}`); + const data = (await res.json()) as ListResp; + + for (const p of data.profiles ?? []) { + if (p.handle && !isJunk(p.handle)) handles.set(p.did, p.handle); + } + for (const r of data.records) { + if (seen.has(r.did) || isJunk(r.did)) continue; + seen.add(r.did); + order.push(r.did); + if (order.length >= maxSites) break; + } + + if (order.length >= maxSites) break; + cursor = data.cursor; + if (!cursor || data.records.length === 0) break; + } + + return order.map((did) => ({ did, handle: handles.get(did) })); +} + +// CLI: `tsx sites.ts` → prints JSON to stdout (MAX_SITES env caps the count). +if (import.meta.url === `file://${process.argv[1]}`) { + const max = process.env.MAX_SITES ? Number(process.env.MAX_SITES) : undefined; + const sites = await listSites({ maxSites: max }); + console.error(`enumerated ${sites.length} sites`); + process.stdout.write(JSON.stringify(sites, null, 2) + '\n'); +} diff --git a/packages/diff-harness/snapshot.ts b/packages/diff-harness/snapshot.ts new file mode 100644 index 00000000..65386510 --- /dev/null +++ b/packages/diff-harness/snapshot.ts @@ -0,0 +1,64 @@ +/** + * Snapshot the rendered HTML of the current live sites — the diff oracle (plan §9). + * Saves diff-snapshots/.html plus a manifest. Run AFTER the open v1 PRs merge so the new + * card types are captured. + * + * NOTE: this captures *rendered output at time T*. To compare a v2 render fairly, the v2 render + * must see the same upstream data the live snapshot baked in — that's the `freeze` step + * (freeze.ts), which can only be built once v2 has data loaders. + */ +import { mkdir, writeFile } from 'node:fs/promises'; +import { listSites, type SiteRef } from './sites.ts'; + +export interface SnapshotOptions { + origin?: string; + outDir?: string; +} + +export interface SnapshotEntry { + id: string; + url: string; + status: number; + ok: boolean; +} + +const sanitize = (id: string) => id.replace(/[^a-z0-9._-]/gi, '_'); + +export async function snapshotSites( + sites: SiteRef[], + opts: SnapshotOptions = {} +): Promise { + const origin = opts.origin ?? process.env.DIFF_LIVE_ORIGIN ?? 'https://blento.app'; + const outDir = opts.outDir ?? new URL('../../diff-snapshots/', import.meta.url).pathname; + await mkdir(outDir, { recursive: true }); + + const manifest: SnapshotEntry[] = []; + for (const site of sites) { + const actor = site.handle ?? site.did; + const url = `${origin}/${actor}`; + try { + const res = await fetch(url, { headers: { 'user-agent': 'blento-diff-harness' } }); + if (res.ok) await writeFile(`${outDir}${sanitize(site.did)}.html`, await res.text()); + manifest.push({ id: site.did, url, status: res.status, ok: res.ok }); + console.error(`${res.ok ? 'saved' : 'skip '} ${actor} (${res.status})`); + } catch (e) { + manifest.push({ id: site.did, url, status: 0, ok: false }); + console.error(`error ${actor}: ${(e as Error).message}`); + } + } + + await writeFile( + `${outDir}manifest.json`, + JSON.stringify({ origin, saved: manifest.filter((m) => m.ok).length, sites: manifest }, null, 2) + ); + return manifest; +} + +// CLI: `tsx snapshot.ts` → enumerate then snapshot (MAX_SITES env caps the count). +if (import.meta.url === `file://${process.argv[1]}`) { + const max = process.env.MAX_SITES ? Number(process.env.MAX_SITES) : undefined; + const sites = await listSites({ maxSites: max }); + console.error(`snapshotting ${sites.length} sites…`); + const manifest = await snapshotSites(sites); + console.error(`done: ${manifest.filter((m) => m.ok).length}/${manifest.length} saved`); +} diff --git a/packages/diff-harness/tsconfig.json b/packages/diff-harness/tsconfig.json new file mode 100644 index 00000000..ab742fc6 --- /dev/null +++ b/packages/diff-harness/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "declaration": false, + "allowImportingTsExtensions": true + }, + "include": ["*.ts"] +} diff --git a/packages/schema/check.ts b/packages/schema/check.ts new file mode 100644 index 00000000..cb59c192 --- /dev/null +++ b/packages/schema/check.ts @@ -0,0 +1,130 @@ +/** + * Validate migrateV1 + serialization against a real repo's LIVE records. + * pnpm --filter @blento/schema exec tsx check.ts [did] + * + * Fetches app.blento.card + app.blento.section from the actor's PDS, migrates per page, and asserts + * the structural invariants (no orphan leaves, integer-only layout, unique sibling ranks, clean + * record round-trip). Network-dependent, so it's a script, not part of the unit suite. + */ +import { migrateV1, buildGraph, nodesToItems, type V1Card, type V1Section } from './src/migrate.js'; +import { nodeToRecord, recordToNode } from './src/serialize.js'; +import { planPageWrite } from './src/write.js'; + +const did = process.argv[2] ?? 'did:plc:s42iw2fbfmgsgh7hdtvvoaao'; + +async function resolvePds(did: string): Promise { + const r = await fetch(`https://plc.directory/${did}`); + const doc: any = await r.json(); + const svc = (doc.service ?? []).find((s: any) => s.id === '#atproto_pds'); + if (!svc) throw new Error('no PDS in DID doc'); + return svc.serviceEndpoint; +} + +async function listAll(pds: string, did: string, collection: string): Promise { + const out: any[] = []; + let cursor: string | undefined; + do { + const url = new URL(`${pds}/xrpc/com.atproto.repo.listRecords`); + url.searchParams.set('repo', did); + url.searchParams.set('collection', collection); + url.searchParams.set('limit', '100'); + if (cursor) url.searchParams.set('cursor', cursor); + const r = await fetch(url); + if (!r.ok) break; + const data: any = await r.json(); + out.push(...(data.records ?? [])); + cursor = data.cursor; + } while (cursor); + return out; +} + +const rkey = (uri: string) => uri.split('/').pop()!; +const normJson = (o: any) => JSON.stringify(o, Object.keys(o).sort()); + +const pds = await resolvePds(did); +const cardRecs = await listAll(pds, did, 'app.blento.card'); +const sectionRecs = await listAll(pds, did, 'app.blento.section'); +const cards: V1Card[] = cardRecs.map((r) => ({ id: rkey(r.uri), ...r.value })); +const sections: V1Section[] = sectionRecs.map((r) => ({ id: rkey(r.uri), ...r.value })); + +// migrate per page (cards/sections partition by page key) +const pageKeys = [ + ...new Set([...cards.map((c) => c.page ?? 'blento.self'), ...sections.map((s) => s.page)]) +]; +const nodes = pageKeys.flatMap((page) => + migrateV1( + sections.filter((s) => s.page === page), + cards.filter((c) => (c.page ?? 'blento.self') === page), + page + ) +); + +const ids = new Set(nodes.map((n) => n.id)); +const leaves = nodes.filter((n) => n.kind === 'leaf'); +const orphans = leaves.filter((n) => n.parent && !ids.has(n.parent)); +const floats = nodes.filter( + (n) => + n.layout && + Object.values(n.layout as Record).some( + (v) => typeof v === 'number' && !Number.isInteger(v) + ) +); +const rankDupes: string[] = []; +const seen = new Map>(); +for (const n of nodes) { + const k = n.parent ?? '__root__'; + const set = seen.get(k) ?? new Set(); + if (set.has(n.rank)) rankDupes.push(`${k}:${n.rank}`); + set.add(n.rank); + seen.set(k, set); +} +const roundTripFail = nodes.filter( + (n) => normJson(recordToNode(n.id, nodeToRecord(n))) !== normJson(n) +); + +// Simulate the full migrate-on-save -> dual-format-read cycle per page: the render projection from +// the written node records must match the legacy (read-from-card/section) projection exactly. +let saveReadFail = 0; +for (const page of pageKeys) { + const ps = sections.filter((s) => s.page === page); + const pc = cards.filter((c) => (c.page ?? 'blento.self') === page); + const legacyView = nodesToItems(buildGraph(ps, pc, page, { order: 'input' })); + const plan = planPageWrite({ + sections: ps, + cards: pc, + page, + updatedAt: 't', + storedNodeIds: [], + legacyCardIds: pc.map((c) => c.id), + legacySectionIds: ps.map((s) => s.id) + }); + const savedView = nodesToItems(plan.nodePuts.map((p) => recordToNode(p.rkey, p.record))); + if (JSON.stringify(savedView) !== JSON.stringify(legacyView)) saveReadFail++; +} + +console.log( + JSON.stringify( + { + did, + pages: pageKeys.length, + cards: cards.length, + sections: sections.length, + nodes: nodes.length, + containers: nodes.filter((n) => n.kind === 'container').length, + leaves: leaves.length, + orphans: orphans.length, + floats: floats.length, + rankDupes: rankDupes.length, + roundTripFail: roundTripFail.length, + saveReadFail + }, + null, + 2 + ) +); + +if (orphans.length || floats.length || rankDupes.length || roundTripFail.length || saveReadFail) { + console.error('INVARIANT FAIL'); + process.exit(1); +} +console.log('OK: all invariants hold'); diff --git a/packages/schema/lexicons/app.blento.node.json b/packages/schema/lexicons/app.blento.node.json new file mode 100644 index 00000000..abf0813f --- /dev/null +++ b/packages/schema/lexicons/app.blento.node.json @@ -0,0 +1,54 @@ +{ + "lexicon": 1, + "id": "app.blento.node", + "defs": { + "main": { + "type": "record", + "key": "tid", + "description": "A single node in a Blento page graph — a container (layout) or a leaf (content unit). The frozen envelope; new types extend the open blobs only.", + "record": { + "type": "object", + "required": ["type", "kind", "rank", "page", "version"], + "properties": { + "type": { + "type": "string", + "description": "Semantic interface key (NSID-style), e.g. 'grid', 'link', 'bluesky'." + }, + "kind": { + "type": "string", + "knownValues": ["document", "container", "leaf"] + }, + "parent": { + "type": "string", + "description": "rkey of the containing node. Omit for a page/document root." + }, + "rank": { + "type": "string", + "description": "Fractional order key among siblings (string, never a float)." + }, + "page": { + "type": "string", + "description": "Denormalized root document id this node belongs to (query/partition key)." + }, + "data": { + "type": "unknown", + "description": "Content blob. Pure JSON, never markup." + }, + "source": { + "type": "unknown", + "description": "Optional declared read capability (atproto-collection | http | subscribe)." + }, + "layout": { + "type": "unknown", + "description": "Position within parent, interpreted by the parent container type. Integers only." + }, + "style": { + "type": "unknown", + "description": "Appearance intent (color role, variant). Never concrete CSS." + }, + "version": { "type": "integer" } + } + } + } + } +} diff --git a/packages/schema/lexicons/app.blento.page.json b/packages/schema/lexicons/app.blento.page.json new file mode 100644 index 00000000..e93e750e --- /dev/null +++ b/packages/schema/lexicons/app.blento.page.json @@ -0,0 +1,30 @@ +{ + "lexicon": 1, + "id": "app.blento.page", + "defs": { + "main": { + "type": "record", + "key": "any", + "description": "A page / document root. Holds page-level config plus the renderer-override stack and token overrides (site -> page -> node cascade).", + "record": { + "type": "object", + "required": ["version"], + "properties": { + "name": { "type": "string", "maxLength": 300 }, + "description": { "type": "string", "maxLength": 2000 }, + "overrides": { + "type": "array", + "description": "Ordered renderer-override provider DIDs; first with an impl for a type wins.", + "items": { "type": "string", "format": "did" } + }, + "style": { + "type": "unknown", + "description": "Page-level semantic token overrides." + }, + "updatedAt": { "type": "string", "format": "datetime" }, + "version": { "type": "integer" } + } + } + } + } +} diff --git a/packages/schema/lexicons/app.blento.theme.json b/packages/schema/lexicons/app.blento.theme.json new file mode 100644 index 00000000..9da1fcaf --- /dev/null +++ b/packages/schema/lexicons/app.blento.theme.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "app.blento.theme", + "defs": { + "main": { + "type": "record", + "key": "tid", + "description": "A portable, forkable theme: a semantic token set plus an optional renderer-provider DID. Referenceable cross-repo by AT-URI.", + "record": { + "type": "object", + "required": ["name", "tokens", "version"], + "properties": { + "name": { "type": "string", "maxLength": 200 }, + "tokens": { + "type": "unknown", + "description": "Semantic design tokens (color roles, radius, spacing, type scale)." + }, + "provider": { + "type": "string", + "format": "did", + "description": "Optional DID publishing custom renderer implementations (pinned/reviewed when hosted)." + }, + "updatedAt": { "type": "string", "format": "datetime" }, + "version": { "type": "integer" } + } + } + } + } +} diff --git a/packages/schema/package.json b/packages/schema/package.json new file mode 100644 index 00000000..7663926c --- /dev/null +++ b/packages/schema/package.json @@ -0,0 +1,23 @@ +{ + "name": "@blento/schema", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./lexicons/*": "./lexicons/*" + }, + "scripts": { + "check": "tsc --noEmit", + "test": "vitest run" + }, + "devDependencies": { + "typescript": "^5.9.3", + "vitest": "^4.1.4" + }, + "dependencies": { + "@atcute/tid": "^1.1.2", + "fractional-indexing": "^3.2.0", + "valibot": "^1.3.1" + } +} diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts new file mode 100644 index 00000000..d1b6cca4 --- /dev/null +++ b/packages/schema/src/index.ts @@ -0,0 +1,5 @@ +export * from './node.js'; +export * from './records.js'; +export * from './migrate.js'; +export * from './serialize.js'; +export * from './write.js'; diff --git a/packages/schema/src/migrate.test.ts b/packages/schema/src/migrate.test.ts new file mode 100644 index 00000000..12101996 --- /dev/null +++ b/packages/schema/src/migrate.test.ts @@ -0,0 +1,123 @@ +import { describe, it, expect } from 'vitest'; +import { migrateV1, migratePage, type V1Card, type V1Section } from './migrate.js'; + +function card(p: Partial): V1Card { + return { + id: 'c1', + cardType: 'link', + x: 0, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 4, + cardData: {}, + version: 2, + ...p + }; +} + +const section = (p: Partial): V1Section => ({ + id: 's1', + sectionType: 'grid', + page: 'blento.self', + index: 0, + sectionData: {}, + ...p +}); + +const stableIds = () => { + let n = 0; + return () => `gen${n++}`; +}; + +describe('migrateV1', () => { + it('maps a section to a container and cards to leaves with layout + style', () => { + const nodes = migrateV1( + [section({ id: 's1' })], + [ + card({ id: 'a', sectionId: 's1', x: 0, y: 2, color: 'red' }), + card({ id: 'b', sectionId: 's1', x: 0, y: 0 }) + ], + 'blento.self' + ); + + expect(nodes.find((n) => n.id === 's1')).toMatchObject({ + kind: 'container', + content: { $type: 'app.blento.defs#container', containerType: 'grid' }, + parent: null, + page: 'blento.self' + }); + + const a = nodes.find((n) => n.id === 'a')!; + expect(a).toMatchObject({ + kind: 'leaf', + content: { $type: 'app.blento.defs#card', cardType: 'link' }, + parent: 's1' + }); + expect(a.layout).toMatchObject({ x: 0, y: 2, w: 2, h: 2, mobileW: 4 }); + expect(a.style).toEqual({ tokens: { color: 'red' } }); + + // document order: b (y=0) ranks before a (y=2) + const b = nodes.find((n) => n.id === 'b')!; + expect(b.rank < a.rank).toBe(true); + expect(b.style).toBeUndefined(); + }); + + it('synthesizes a default grid container when there are no sections', () => { + const nodes = migrateV1([], [card({ id: 'x' })], 'blento.self', { genId: stableIds() }); + const container = nodes.find((n) => n.kind === 'container')!; + expect(container.content.containerType).toBe('grid'); + expect(container.id).toBe('gen0'); + expect(nodes.find((n) => n.id === 'x')!.parent).toBe('gen0'); + }); + + it('reparents orphan cards (dangling sectionId) to the default grid container', () => { + const nodes = migrateV1( + [section({ id: 's1' })], + [card({ id: 'o', sectionId: 'does-not-exist' })], + 'p' + ); + expect(nodes.find((n) => n.id === 'o')!.parent).toBe('s1'); + }); + + it('subsumes v1 coord-doubling for unversioned cards', () => { + const nodes = migrateV1( + [section({ id: 's1' })], + [card({ id: 'c', sectionId: 's1', x: 1, y: 1, w: 1, h: 1, version: undefined })], + 'p' + ); + expect(nodes.find((n) => n.id === 'c')!.layout).toMatchObject({ x: 2, y: 2, w: 2, h: 2 }); + }); + + it('produces unique, ordered ranks within a parent', () => { + const cards = Array.from({ length: 5 }, (_, i) => + card({ id: `c${i}`, sectionId: 's1', x: i, y: 0 }) + ); + const nodes = migrateV1([section({ id: 's1' })], cards, 'p'); + const ranks = nodes.filter((n) => n.kind === 'leaf').map((n) => n.rank); + expect(new Set(ranks).size).toBe(5); + expect([...ranks].sort()).toEqual(ranks); // already in ascending rank order (x ascending) + }); +}); + +describe('migratePage', () => { + it('maps accent/base colors to semantic token roles + carries name/description', () => { + const page = migratePage({ + name: 'My Site', + description: 'hi', + preferences: { accentColor: 'pink', baseColor: 'stone' } + }); + expect(page.style?.colors).toEqual({ accent: 'pink', base: 'stone' }); + expect(page.name).toBe('My Site'); + expect(page.description).toBe('hi'); + expect(page.version).toBe(1); + }); + + it('omits style when no theme prefs are set', () => { + expect(migratePage({ name: 'x' }).style).toBeUndefined(); + expect(migratePage(undefined)).toMatchObject({ $type: 'app.blento.page', version: 1 }); + }); +}); diff --git a/packages/schema/src/migrate.ts b/packages/schema/src/migrate.ts new file mode 100644 index 00000000..708678d7 --- /dev/null +++ b/packages/schema/src/migrate.ts @@ -0,0 +1,276 @@ +/** + * v1 → v2 migration. Reads v1 records (app.blento.card + app.blento.section + publication) and + * produces v2 nodes + a page record. See plan §2 (mapping) and §10 (scar tissue to subsume). + * + * section -> container node (id preserved; index -> rank) + * card -> leaf node (sectionId -> parent; coords + rotation -> layout; color -> style) + * no sections -> synthesize a default `grid` container (cf. v1 ensureSections) + * + * Subsumes v1's own card migration ladder (version<1 doubled coords; version<2 defaulted page). + */ +import * as TID from '@atcute/tid'; +import { generateNKeysBetween } from 'fractional-indexing'; +import { CARD_CONTENT, CONTAINER_CONTENT, GRID_CELL_LAYOUT, type Node } from './node.js'; +import type { PageRecord, StyleTokens } from './records.js'; + +export interface V1Card { + id: string; + cardType: string; + sectionId?: string; + x: number; + y: number; + w: number; + h: number; + mobileX: number; + mobileY: number; + mobileW: number; + mobileH: number; + color?: string; + rotation?: number; + cardData: unknown; + page?: string; + version?: number; +} + +export interface V1Section { + id: string; + sectionType: string; + page: string; + index: number; + sectionData: Record; +} + +export interface V1Publication { + url?: string; + name?: string; + description?: string; + preferences?: { + accentColor?: string; + baseColor?: string; + hideProfileSection?: boolean; + profilePosition?: 'side' | 'top'; + editedOn?: number; + layoutMode?: string; + }; +} + +export interface MigrateOptions { + /** Id generator for synthesized containers. Defaults to a TID; tests can inject a stable one. */ + genId?: () => string; + /** + * Leaf ordering within a container. + * - 'document' (default): sort by (y, x, id) — canonical order for a fresh migration / write. + * - 'input': preserve the caller's array order — used on the read path so DOM order matches prod. + */ + order?: 'document' | 'input'; +} + +/** Apply v1's own card migration ladder so we ingest normalized coordinates. */ +function normalizeV1Card(input: V1Card): V1Card { + const c = { ...input }; + if (!c.version) { + c.x *= 2; + c.y *= 2; + c.w *= 2; + c.h *= 2; + c.mobileX *= 2; + c.mobileY *= 2; + c.mobileW *= 2; + c.mobileH *= 2; + c.version = 1; + } + if (!c.version || c.version < 2) { + c.page ??= 'blento.self'; + c.version = 2; + } + return c; +} + +/** + * Structural transform: v1 sections + cards -> node graph. Does NOT run the coordinate ladder, so + * coordinates pass through verbatim — callers reading already-normalized records (the live load + * path) get the same geometry as prod. `migrateV1` layers the ladder on top for raw ingestion. + */ +export function buildGraph( + sections: V1Section[], + cards: V1Card[], + page: string, + opts: MigrateOptions = {} +): Node[] { + const genId = opts.genId ?? (() => TID.now()); + const nodes: Node[] = []; + + // 1. Sections -> container nodes, ordered by index. Synthesize a grid container if none exist. + let sectionList = [...sections].sort((a, b) => a.index - b.index); + if (sectionList.length === 0) { + sectionList = [{ id: genId(), sectionType: 'grid', page, index: 0, sectionData: {} }]; + } + const sectionIds = new Set(sectionList.map((s) => s.id)); + const sectionRanks = generateNKeysBetween(null, null, sectionList.length); + sectionList.forEach((s, i) => { + nodes.push({ + id: s.id, + kind: 'container', + parent: null, + rank: sectionRanks[i], + page, + // Generic container fallback: containerType discriminates; sectionData spread as fields. + content: { ...(s.sectionData ?? {}), $type: CONTAINER_CONTENT, containerType: s.sectionType }, + version: 1 + }); + }); + + // Orphan cards (sectionId missing or dangling) go to the first grid container, else the first. + const defaultContainerId = + sectionList.find((s) => s.sectionType === 'grid')?.id ?? sectionList[0].id; + + // 2. Cards -> leaf nodes, grouped by parent container. + const byParent = new Map(); + for (const c of cards) { + const parent = c.sectionId && sectionIds.has(c.sectionId) ? c.sectionId : defaultContainerId; + let group = byParent.get(parent); + if (!group) { + group = []; + byParent.set(parent, group); + } + group.push(c); + } + + for (const [parent, group] of byParent) { + if ((opts.order ?? 'document') === 'document') { + // Deterministic document order within a grid: by (y, x), then id as a tiebreaker. + group.sort((a, b) => a.y - b.y || a.x - b.x || a.id.localeCompare(b.id)); + } + const ranks = generateNKeysBetween(null, null, group.length); + group.forEach((c, i) => { + const cardData = + c.cardData && typeof c.cardData === 'object' ? (c.cardData as Record) : {}; + const node: Node = { + id: c.id, + kind: 'leaf', + parent, + rank: ranks[i], + page, + // Generic card fallback: cardType discriminates; cardData spread as fields. + content: { ...cardData, $type: CARD_CONTENT, cardType: c.cardType }, + layout: { + $type: GRID_CELL_LAYOUT, + x: c.x, + y: c.y, + w: c.w, + h: c.h, + mobileX: c.mobileX, + mobileY: c.mobileY, + mobileW: c.mobileW, + mobileH: c.mobileH, + ...(c.rotation ? { rotation: c.rotation } : {}) + }, + version: 1 + }; + if (c.color) node.style = { tokens: { color: c.color } }; + nodes.push(node); + }); + } + + return nodes; +} + +/** + * Full v1 -> node migration for INGESTING raw records (one-time / migrate-on-save): runs v1's + * coordinate ladder, then builds the graph in canonical document order. + */ +export function migrateV1( + sections: V1Section[], + cards: V1Card[], + page: string, + opts: MigrateOptions = {} +): Node[] { + return buildGraph(sections, cards.map(normalizeV1Card), page, opts); +} + +/** A v1-shaped container (section), reconstructed from a node for the existing render/editor. */ +export interface LegacySection extends V1Section { + name?: string; + version?: number; +} + +const byRank = (a: Node, b: Node) => (a.rank < b.rank ? -1 : a.rank > b.rank ? 1 : 0); + +/** + * Inverse of `buildGraph`: node graph -> v1-shaped { sections, cards } so v1's existing render and + * editor keep working while storage moves to nodes. Containers -> sections (rank order -> index), + * leaves -> cards (layout -> coords, style.color -> color, parent -> sectionId). + */ +export function nodesToItems(nodes: Node[]): { sections: LegacySection[]; cards: V1Card[] } { + const num = (v: unknown) => (typeof v === 'number' ? v : 0); + + const sections: LegacySection[] = nodes + .filter((n) => n.kind === 'container') + .sort(byRank) + .map((c, i) => { + const sectionData = { ...c.content } as Record; + const containerType = sectionData.containerType as string | undefined; + delete sectionData.$type; + delete sectionData.containerType; + return { + id: c.id, + sectionType: containerType ?? 'grid', + page: c.page, + index: i, + sectionData, + version: c.version + }; + }); + + const cards: V1Card[] = nodes + .filter((n) => n.kind === 'leaf') + .sort(byRank) + .map((n) => { + const cardData = { ...n.content } as Record; + const cardType = cardData.cardType as string | undefined; + delete cardData.$type; + delete cardData.cardType; + const l = (n.layout ?? {}) as Record; + const card: V1Card = { + id: n.id, + cardType: cardType ?? 'text', + cardData, + x: num(l.x), + y: num(l.y), + w: num(l.w), + h: num(l.h), + mobileX: num(l.mobileX), + mobileY: num(l.mobileY), + mobileW: num(l.mobileW), + mobileH: num(l.mobileH), + sectionId: n.parent ?? undefined, + page: n.page, + version: n.version + }; + if (typeof l.rotation === 'number') card.rotation = l.rotation; + const color = n.style?.tokens?.color; + if (color) card.color = color; + return card; + }); + + return { sections, cards }; +} + +/** + * Migrate a v1 publication into a v2 page record. Theme colors map to semantic token roles; + * profile/layout prefs (hideProfileSection, profilePosition, editedOn) are handled when the profile + * container is built in Phase 5 — preserved on the caller side until then. + */ +export function migratePage(pub: V1Publication | undefined): PageRecord { + const prefs = pub?.preferences ?? {}; + const colors: Record = {}; + if (prefs.accentColor) colors.accent = prefs.accentColor; + if (prefs.baseColor) colors.base = prefs.baseColor; + const style: StyleTokens | undefined = Object.keys(colors).length ? { colors } : undefined; + + const record: PageRecord = { $type: 'app.blento.page', version: 1 }; + if (pub?.name) record.name = pub.name; + if (pub?.description) record.description = pub.description; + if (style) record.style = style; + return record; +} diff --git a/packages/schema/src/node.ts b/packages/schema/src/node.ts new file mode 100644 index 00000000..6fe06786 --- /dev/null +++ b/packages/schema/src/node.ts @@ -0,0 +1,84 @@ +/** + * The Blento node envelope — the stored record shape. + * + * Two decoupled typed axes: `content.$type` is the DATA shape (an open union of `app.blento.defs` + * members — interop), while `style.renderer` is the RENDER type (defaults from `content.$type`, + * overridable). There is deliberately NO top-level `type` string. New card / layout / theme types + * only add members to the open blobs (`content`, `layout`, `style`, `source`) and register a + * renderer/def — the envelope never changes. See ../../../blento-schema-design.md. + */ + +/** Structural role. Drives editor/runtime treatment, not the specific renderer. */ +export type NodeKind = 'document' | 'container' | 'leaf'; + +/** CONTENT — the data. `$type` = the data shape (open union of `app.blento.defs`). */ +export interface Content { + $type: string; + [k: string]: unknown; +} + +/** POSITION within the parent, typed by the parent container's contract (e.g. `#gridCell`). */ +export interface Layout { + $type: string; + [k: string]: unknown; +} + +/** PRESENTATION intent. Never concrete CSS. */ +export interface Style { + /** Renderer id/ref override; the default resolves from `content.$type`. */ + renderer?: string; + /** Semantic design tokens (color roles, etc.). */ + tokens?: Record; + [k: string]: unknown; +} + +/** + * A declared read. Resolved by trusted first-party loaders; renderers only ever receive + * already-fetched JSON. `atproto` is any XRPC query (output typed by the method's lexicon or the + * explicit `outputs`); `ref` shares another node's loaded data. See the design doc §Source. + */ +export type Source = + | { + $type: 'app.blento.source#atproto'; + method: string; + params?: Record; + service?: string; + outputs?: unknown; + } + | { $type: 'app.blento.source#http'; url: string; outputs?: unknown } + | { + $type: 'app.blento.source#custom'; + loader: string; + params?: Record; + outputs?: unknown; + } + | { $type: 'app.blento.source#ref'; node: string }; + +export interface Node { + /** rkey (TID). */ + id: string; + /** Structural role. */ + kind: NodeKind; + /** Containing node id. null = page/document root. */ + parent: string | null; + /** Order among siblings — a STRING fractional key (never a float; atproto forbids floats). */ + rank: string; + /** Denormalized partition/query key: the root document id this node belongs to. */ + page: string; + /** The data. `content.$type` is the shape and the default renderer key. */ + content: Content; + /** Position within the parent. Integers only. */ + layout?: Layout; + /** Appearance intent (renderer override + tokens). */ + style?: Style; + /** Declared read capability — inspectable without running renderers. */ + source?: Source; + version: number; +} + +export const SCHEMA_VERSION = 1; + +/** Def NSIDs. The migration uses the generic fallbacks; typed defs (#link, #image, …) are additive. */ +export const CARD_CONTENT = 'app.blento.defs#card'; +export const CONTAINER_CONTENT = 'app.blento.defs#container'; +export const GRID_CELL_LAYOUT = 'app.blento.defs#gridCell'; diff --git a/packages/schema/src/records.ts b/packages/schema/src/records.ts new file mode 100644 index 00000000..09895593 --- /dev/null +++ b/packages/schema/src/records.ts @@ -0,0 +1,38 @@ +/** + * Top-level records: page (document root) and theme. Site record is deferred (single implicit + * site in v1 — see plan §2). The renderer-resolution + token cascade lives here, never on nodes. + */ + +/** Semantic design-token intent. Concrete values resolved by the active theme at render time. */ +export interface StyleTokens { + /** Color roles, e.g. { accent: 'pink', surface: 'stone', text: 'auto' }. */ + colors?: Record; + radius?: Record; + space?: Record; + font?: Record; + [group: string]: Record | undefined; +} + +export interface PageRecord { + /** rkey is the page id, e.g. 'blento.self' or 'blento.'. */ + $type: 'app.blento.page'; + name?: string; + description?: string; + /** Ordered renderer-override provider DIDs. First with an impl for a type wins. (plan §4) */ + overrides?: string[]; + /** Page-level token overrides (over site/host defaults). */ + style?: StyleTokens; + updatedAt?: string; + version: number; +} + +export interface ThemeRecord { + /** A portable, forkable theme: a token set + optional renderer-provider DID. */ + $type: 'app.blento.theme'; + name: string; + tokens: StyleTokens; + /** Optional DID that publishes custom renderer implementations (pinned/reviewed when hosted). */ + provider?: string; + updatedAt?: string; + version: number; +} diff --git a/packages/schema/src/roundtrip.test.ts b/packages/schema/src/roundtrip.test.ts new file mode 100644 index 00000000..787c6949 --- /dev/null +++ b/packages/schema/src/roundtrip.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect } from 'vitest'; +import { + buildGraph, + nodesToItems, + nodeToRecord, + recordToNode, + type V1Card, + type V1Section +} from './index.js'; + +const grid: V1Section = { id: 's', sectionType: 'grid', page: 'p', index: 0, sectionData: {} }; +const cards: V1Card[] = [ + { + id: 'c1', + cardType: 'link', + sectionId: 's', + x: 0, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 2, + color: 'rose', + rotation: 3, + cardData: { url: 'https://example.com' }, + version: 2 + }, + { + id: 'c2', + cardType: 'image', + sectionId: 's', + x: 2, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 2, + mobileW: 4, + mobileH: 2, + cardData: { cid: 'abc' }, + version: 2 + } +]; + +describe('full storage round-trip (write → records → read)', () => { + it('cards/sections → nodes → records → nodes → cards/sections is render-lossless', () => { + const nodes = buildGraph([grid], cards, 'p', { order: 'input' }); + const direct = nodesToItems(nodes); + // simulate persistence: each node -> app.blento.node record -> node (the write/read boundary) + const reread = nodes.map((n) => recordToNode(n.id, nodeToRecord(n))); + expect(nodesToItems(reread)).toEqual(direct); + }); +}); diff --git a/packages/schema/src/serialize.test.ts b/packages/schema/src/serialize.test.ts new file mode 100644 index 00000000..19a8a506 --- /dev/null +++ b/packages/schema/src/serialize.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'vitest'; +import { migrateV1, type V1Card, type V1Section } from './migrate.js'; +import { nodeToRecord, recordToNode } from './serialize.js'; + +const grid: V1Section = { + id: 'sec1', + sectionType: 'grid', + page: 'blento.self', + index: 0, + sectionData: {} +}; + +describe('node <-> record round-trip', () => { + it('round-trips a migrated graph (record drops id + root parent, restores them)', () => { + const cards: V1Card[] = [ + { + id: 'card1', + cardType: 'link', + sectionId: 'sec1', + x: 0, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 2, + color: 'rose', + cardData: { url: 'https://example.com' }, + version: 2 + } + ]; + const nodes = migrateV1([grid], cards, 'blento.self'); + for (const node of nodes) { + expect(recordToNode(node.id, nodeToRecord(node))).toEqual(node); + } + }); + + it('root container: parent omitted in the record, null on the node', () => { + const node = migrateV1([grid], [], 'blento.self')[0]; + const rec = nodeToRecord(node); + expect(rec.parent).toBeUndefined(); + expect(recordToNode(node.id, rec).parent).toBeNull(); + }); + + it('layout is integer-only (atproto forbids floats)', () => { + const cards: V1Card[] = [ + { + id: 'c', + cardType: 'text', + sectionId: 'sec1', + x: 1, + y: 2, + w: 3, + h: 4, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 4, + cardData: {}, + version: 2 + } + ]; + const leaf = migrateV1([grid], cards, 'blento.self').find((n) => n.kind === 'leaf')!; + for (const v of Object.values(leaf.layout as Record)) { + if (typeof v === 'number') expect(Number.isInteger(v)).toBe(true); + } + }); +}); diff --git a/packages/schema/src/serialize.ts b/packages/schema/src/serialize.ts new file mode 100644 index 00000000..00bdb2c4 --- /dev/null +++ b/packages/schema/src/serialize.ts @@ -0,0 +1,55 @@ +/** + * Node <-> `app.blento.node` record serialization. + * + * The in-memory Node carries `id` (= the record's rkey) and a non-null `parent` sentinel; the stored + * record omits both the rkey (it's the key) and `parent` when at the document root. Undefined blobs + * are dropped so records stay minimal. + */ +import type { Content, Layout, Node, NodeKind, Source, Style } from './node.js'; + +export interface NodeRecord { + $type: 'app.blento.node'; + kind: NodeKind; + parent?: string; + rank: string; + page: string; + content: Content; + layout?: Layout; + style?: Style; + source?: Source; + updatedAt?: string; + version: number; +} + +export function nodeToRecord(node: Node, updatedAt?: string): NodeRecord { + const rec: NodeRecord = { + $type: 'app.blento.node', + kind: node.kind, + rank: node.rank, + page: node.page, + content: node.content, + version: node.version + }; + if (node.parent != null) rec.parent = node.parent; + if (node.source !== undefined) rec.source = node.source; + if (node.layout !== undefined) rec.layout = node.layout; + if (node.style !== undefined) rec.style = node.style; + if (updatedAt) rec.updatedAt = updatedAt; + return rec; +} + +export function recordToNode(rkey: string, rec: NodeRecord): Node { + const node: Node = { + id: rkey, + kind: rec.kind, + parent: rec.parent ?? null, + rank: rec.rank, + page: rec.page, + content: rec.content ?? { $type: 'app.blento.defs#card' }, + version: rec.version ?? 1 + }; + if (rec.source !== undefined) node.source = rec.source; + if (rec.layout !== undefined) node.layout = rec.layout; + if (rec.style !== undefined) node.style = rec.style; + return node; +} diff --git a/packages/schema/src/view.test.ts b/packages/schema/src/view.test.ts new file mode 100644 index 00000000..2e7b6d14 --- /dev/null +++ b/packages/schema/src/view.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from 'vitest'; +import { buildGraph, nodesToItems, type V1Card, type V1Section } from './migrate.js'; + +const grid: V1Section = { id: 's', sectionType: 'grid', page: 'p', index: 0, sectionData: {} }; + +describe('buildGraph + nodesToItems (read path)', () => { + it('buildGraph passes coords through verbatim (no ladder), even for version-less cards', () => { + const cards: V1Card[] = [ + { + id: 'c', + cardType: 'text', + sectionId: 's', + x: 3, + y: 5, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 2, + cardData: {} + } // no version — migrateV1 would double these; buildGraph must not + ]; + const leaf = buildGraph([grid], cards, 'p').find((n) => n.kind === 'leaf')!; + expect((leaf.layout as Record).x).toBe(3); + expect((leaf.layout as Record).y).toBe(5); + }); + + it('nodesToItems inverts buildGraph into render-equivalent sections/cards', () => { + const cards: V1Card[] = [ + { + id: 'c1', + cardType: 'link', + sectionId: 's', + x: 0, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 2, + color: 'rose', + cardData: { url: 'https://example.com' }, + version: 2 + }, + { + id: 'c2', + cardType: 'text', + sectionId: 's', + x: 2, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 2, + mobileW: 4, + mobileH: 2, + cardData: {}, + version: 2 + } + ]; + const view = nodesToItems(buildGraph([grid], cards, 'p', { order: 'input' })); + expect(view.sections).toHaveLength(1); + expect(view.sections[0]).toMatchObject({ id: 's', sectionType: 'grid', page: 'p', index: 0 }); + expect(view.cards).toHaveLength(2); + const c1 = view.cards.find((c) => c.id === 'c1')!; + expect(c1).toMatchObject({ + cardType: 'link', + x: 0, + y: 0, + w: 2, + h: 2, + sectionId: 's', + color: 'rose' + }); + expect(c1.cardData).toEqual({ url: 'https://example.com' }); + }); + + it('synthesizes a grid section when none exist and assigns orphan cards to it', () => { + const cards: V1Card[] = [ + { + id: 'c', + cardType: 'image', + x: 0, + y: 0, + w: 4, + h: 4, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 4, + cardData: {}, + version: 2 + } + ]; + const view = nodesToItems(buildGraph([], cards, 'p', { order: 'input' })); + expect(view.sections).toHaveLength(1); + expect(view.sections[0].sectionType).toBe('grid'); + expect(view.cards[0].sectionId).toBe(view.sections[0].id); + }); +}); diff --git a/packages/schema/src/write.test.ts b/packages/schema/src/write.test.ts new file mode 100644 index 00000000..46c8f228 --- /dev/null +++ b/packages/schema/src/write.test.ts @@ -0,0 +1,82 @@ +import { describe, it, expect } from 'vitest'; +import { planPageWrite } from './write.js'; +import type { V1Card, V1Section } from './migrate.js'; + +const grid: V1Section = { + id: 'sec', + sectionType: 'grid', + page: 'blento.self', + index: 0, + sectionData: {} +}; +const card = (id: string, x: number): V1Card => ({ + id, + cardType: 'link', + sectionId: 'sec', + x, + y: 0, + w: 2, + h: 2, + mobileX: 0, + mobileY: 0, + mobileW: 4, + mobileH: 2, + cardData: { url: `https://example.com/${id}` }, + version: 2 +}); + +describe('planPageWrite', () => { + it('first migration (legacy page): writes nodes, retires the legacy card/section records', () => { + const plan = planPageWrite({ + sections: [grid], + cards: [card('c1', 0), card('c2', 2)], + page: 'blento.self', + updatedAt: '2026-01-01T00:00:00Z', + storedNodeIds: [], // legacy: no node records yet + legacyCardIds: ['c1', 'c2'], + legacySectionIds: ['sec'] + }); + // one container + two leaves + expect(plan.nodePuts.map((p) => p.rkey).sort()).toEqual(['c1', 'c2', 'sec']); + expect(plan.nodePuts.every((p) => p.record.$type === 'app.blento.node')).toBe(true); + // legacy records retired + expect(plan.cardDeletes.sort()).toEqual(['c1', 'c2']); + expect(plan.sectionDeletes).toEqual(['sec']); + expect(plan.nodeDeletes).toEqual([]); + }); + + it('already migrated: no legacy deletes; removed cards delete their node records', () => { + const plan = planPageWrite({ + sections: [grid], + cards: [card('c1', 0)], // c2 removed in the editor + page: 'blento.self', + updatedAt: '2026-01-01T00:00:00Z', + storedNodeIds: ['sec', 'c1', 'c2'], // already on nodes + legacyCardIds: [], + legacySectionIds: [] + }); + expect(plan.nodePuts.map((p) => p.rkey).sort()).toEqual(['c1', 'sec']); + expect(plan.nodeDeletes).toEqual(['c2']); // the removed card's node record + expect(plan.cardDeletes).toEqual([]); + expect(plan.sectionDeletes).toEqual([]); + }); + + it('node records carry the node envelope (kind, layout, page, updatedAt)', () => { + const plan = planPageWrite({ + sections: [grid], + cards: [card('c1', 0)], + page: 'blento.self', + updatedAt: '2026-06-29T00:00:00Z', + storedNodeIds: [], + legacyCardIds: ['c1'], + legacySectionIds: ['sec'] + }); + const leaf = plan.nodePuts.find((p) => p.rkey === 'c1')!.record; + expect(leaf.kind).toBe('leaf'); + expect(leaf.page).toBe('blento.self'); + expect(leaf.updatedAt).toBe('2026-06-29T00:00:00Z'); + expect((leaf.layout as Record).x).toBe(0); + const container = plan.nodePuts.find((p) => p.rkey === 'sec')!.record; + expect(container.kind).toBe('container'); + }); +}); diff --git a/packages/schema/src/write.ts b/packages/schema/src/write.ts new file mode 100644 index 00000000..4fda8c21 --- /dev/null +++ b/packages/schema/src/write.ts @@ -0,0 +1,43 @@ +/** + * Migrate-on-save planning. Given the editor's current sections/cards plus what was loaded, compute + * the exact record mutations: write the node graph, and retire the legacy card/section records the + * first time a page is migrated. Pure + deterministic so it can be unit-tested without a PDS; the + * caller (save.ts) executes the plan with the owner's OAuth session. + */ +import { buildGraph, type V1Card, type V1Section } from './migrate.js'; +import { nodeToRecord, type NodeRecord } from './serialize.js'; + +export interface PageWritePlan { + /** app.blento.node records to put (rkey = node id). */ + nodePuts: { rkey: string; record: NodeRecord }[]; + /** app.blento.node rkeys to delete (nodes removed since load). */ + nodeDeletes: string[]; + /** app.blento.card rkeys to retire (legacy cleanup; empty once the page is on nodes). */ + cardDeletes: string[]; + /** app.blento.section rkeys to retire (legacy cleanup; empty once the page is on nodes). */ + sectionDeletes: string[]; +} + +export interface PlanPageWriteInput { + sections: V1Section[]; + /** Current cards (post-upload), each carrying its rkey as `id`. */ + cards: V1Card[]; + page: string; + updatedAt: string; + /** ids of the app.blento.node records currently stored (empty if the page is still legacy). */ + storedNodeIds: string[]; + /** ids of the legacy card/section records to retire (empty once the page is already on nodes). */ + legacyCardIds: string[]; + legacySectionIds: string[]; +} + +export function planPageWrite(input: PlanPageWriteInput): PageWritePlan { + const nodes = buildGraph(input.sections, input.cards, input.page, { order: 'input' }); + const newIds = new Set(nodes.map((n) => n.id)); + return { + nodePuts: nodes.map((n) => ({ rkey: n.id, record: nodeToRecord(n, input.updatedAt) })), + nodeDeletes: input.storedNodeIds.filter((id) => !newIds.has(id)), + cardDeletes: input.legacyCardIds, + sectionDeletes: input.legacySectionIds + }; +} diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json new file mode 100644 index 00000000..df59da57 --- /dev/null +++ b/packages/schema/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6882545..8ded21a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,21 @@ settings: importers: .: + devDependencies: + prettier: + specifier: ^3.8.1 + version: 3.8.1 + prettier-plugin-svelte: + specifier: ^3.5.1 + version: 3.5.1(prettier@3.8.1)(svelte@5.53.11) + prettier-plugin-tailwindcss: + specifier: ^0.7.2 + version: 0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.11))(prettier@3.8.1) + turbo: + specifier: ^2.5.0 + version: 2.10.0 + + apps/web: dependencies: '@atcute/atproto': specifier: ^3.1.10 @@ -47,6 +62,9 @@ importers: '@atmo-dev/events-ui': specifier: ^0.1.0 version: 0.1.0(72359c36614541bbedb30259ef448b88) + '@blento/schema': + specifier: workspace:* + version: link:../../packages/schema '@cloudflare/workers-types': specifier: ^4.20260313.1 version: 4.20260313.1 @@ -172,7 +190,7 @@ importers: version: 5.20.0 marked: specifier: ^17.0.4 - version: 17.0.4 + version: 17.0.6 perfect-freehand: specifier: ^1.2.3 version: 1.2.3 @@ -266,13 +284,13 @@ importers: version: 17.4.0 prettier: specifier: ^3.8.1 - version: 3.8.1 + version: 3.8.3 prettier-plugin-svelte: specifier: ^3.5.1 - version: 3.5.1(prettier@3.8.1)(svelte@5.53.11) + version: 3.5.1(prettier@3.8.3)(svelte@5.53.11) prettier-plugin-tailwindcss: specifier: ^0.7.2 - version: 0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.11))(prettier@3.8.1) + version: 0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.53.11))(prettier@3.8.3) svelte: specifier: ^5.53.11 version: 5.53.11 @@ -304,6 +322,62 @@ importers: specifier: ^4.1.4 version: 4.1.4(@types/node@25.9.1)(vite@8.0.0(@types/node@25.9.1)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) + packages/backup: + devDependencies: + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + + packages/diff-harness: + dependencies: + node-html-parser: + specifier: ^7.0.1 + version: 7.1.0 + pixelmatch: + specifier: ^6.0.0 + version: 6.0.0 + playwright: + specifier: ^1.50.0 + version: 1.61.1 + pngjs: + specifier: ^7.0.0 + version: 7.0.0 + devDependencies: + '@types/pngjs': + specifier: ^6.0.5 + version: 6.0.5 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.1.4 + version: 4.1.4(@types/node@25.9.1)(vite@8.0.0(@types/node@25.9.1)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) + + packages/schema: + dependencies: + '@atcute/tid': + specifier: ^1.1.2 + version: 1.1.2 + fractional-indexing: + specifier: ^3.2.0 + version: 3.4.0 + valibot: + specifier: ^1.3.1 + version: 1.3.1(typescript@5.9.3) + devDependencies: + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.1.4 + version: 4.1.4(@types/node@25.9.1)(vite@8.0.0(@types/node@25.9.1)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) + packages: '@atcute/atproto@3.1.10': @@ -338,9 +412,6 @@ packages: peerDependencies: '@atcute/identity': ^1.0.0 - '@atcute/identity@1.1.3': - resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} - '@atcute/identity@1.1.4': resolution: {integrity: sha512-RCw1IqflfuSYCxK5m0lZCm0UnvIzcUnuhngiBhJEJb9a9Mc2SEf1xP3H8N5r8pvEH1LoAYd6/zrvCNU+uy9esw==} @@ -366,9 +437,6 @@ packages: '@atcute/mst@1.0.0': resolution: {integrity: sha512-pMce2efib+dmKtnGnIvJZitVncJkpr3AmhyfgfYllni8KzsaDGsJmuGavSVpuojAhQe+6jYwHFtpm/beiiH4uw==} - '@atcute/multibase@1.1.8': - resolution: {integrity: sha512-pJgtImMZKCjqwRbu+2GzB+4xQjKBXDwdZOzeqe0u97zYKRGftpGYGvYv3+pMe2xXe+msDyu7Nv8iJp+U14otTA==} - '@atcute/multibase@1.2.0': resolution: {integrity: sha512-ZK2GRra+qIYq9nNuQB52m2ul0hOmCQEtPobGfTSUxm7pF0OGEkWGkWHugFhNEDVzHzTwPxHp6VGotdZFue4lYQ==} @@ -405,9 +473,6 @@ packages: '@atcute/util-fetch@1.0.5': resolution: {integrity: sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig==} - '@atcute/util-text@1.2.0': - resolution: {integrity: sha512-b8WSh+Z7K601eUFFmTFj8QPKDO8Ic0VDDj63sdKzpkm+ySQKsYT5nXekViGqFVKbyKj1V5FyvZvgXad6/aI4QQ==} - '@atcute/util-text@1.3.1': resolution: {integrity: sha512-MRgJXkx67znuBXuoAYCJkBZyd3OApL7zZlNf5kXhuoCXcdiu1nblRDycYTADSkym4epBSQWxh26kmI9sewaq6A==} @@ -1606,6 +1671,36 @@ packages: '@tiptap/core': ^3.20.1 '@tiptap/pm': ^3.20.1 + '@turbo/darwin-64@2.10.0': + resolution: {integrity: sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.10.0': + resolution: {integrity: sha512-9d2fTyyG0lf5Wq1bwJA9qUaeecViMkLcdctWaMMmCkxZ/JqypmqOwK3W6vmejeKVgkr06gSoiX8bD+xN5Jpxcg==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.10.0': + resolution: {integrity: sha512-sZBtjMuufitanjzi6UssoUpJMnnPlLMcdcJj3m3ptNsSq31Xh7MnjhwA5nWvLDTfEFg8GPcbYFXMo8vSdKRfqQ==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.10.0': + resolution: {integrity: sha512-vkq/Z8R+1DQ+kifWFa810IjRy2NNBVvha3cg9sWA3nFh6nnGrHSMnnJKrzH7c/No9kq4Jb55Ru44YKsCSBgrKg==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.10.0': + resolution: {integrity: sha512-CRUEguLWxFQHptYZS7HjPhNhAFawfea07iR+xAQ5e4klgLrPCMdexBkXwSCwOxqTFknJ7RZFN3gOaADsw+Gttg==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.10.0': + resolution: {integrity: sha512-dVHGaf9F8twzgibcBqKoADT/LLqf9++jDb+hq/LPWWaOmRpp4M+/pVOm7vy4z9D++xg8eaxWLT0+wQxFwhYu9A==} + cpu: [arm64] + os: [win32] + '@tweenjs/tween.js@23.1.3': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} @@ -1648,6 +1743,9 @@ packages: '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@types/pngjs@6.0.5': + resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} + '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} @@ -2158,6 +2256,15 @@ packages: flatted@3.4.1: resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + fractional-indexing@3.4.0: + resolution: {integrity: sha512-8J3glhz2rrpKG6KmI7wmJo3zH1VjeOpN+vTJSw1fOyO+Viqq3zX6/5NGh6oaZB2qIAYdOYuu5Dz9xp4faOO0Pg==} + engines: {node: ^14.13.1 || >=16.0.0} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2187,6 +2294,10 @@ packages: gsap@3.14.2: resolution: {integrity: sha512-P8/mMxVLU7o4+55+1TCnQrPmgjPKnwkzkXOK1asnR9Jg2lna4tEY5qBJjMmAaOBDDZWtlRjBXjLa0w53G/uBLA==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hex-rgb@4.3.0: resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==} engines: {node: '>=6'} @@ -2495,11 +2606,6 @@ packages: resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true - marked@17.0.4: - resolution: {integrity: sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==} - engines: {node: '>= 20'} - hasBin: true - marked@17.0.6: resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} engines: {node: '>= 20'} @@ -2562,14 +2668,12 @@ packages: engines: {node: ^18 || >=20} hasBin: true - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} - engines: {node: ^18 || >=20} - hasBin: true - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-html-parser@7.1.0: + resolution: {integrity: sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ==} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -2653,15 +2757,33 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pixelmatch@6.0.0: + resolution: {integrity: sha512-FYpL4XiIWakTnIqLqvt3uN4L9B3TsuHIvhLILzTiJZMJUsGvmKNeL4H3b6I99LRyerK9W4IuOXw+N28AtRgK2g==} + hasBin: true + pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + playwright-core@1.61.1: + resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.61.1: + resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==} + engines: {node: '>=18'} + hasBin: true + plyr@3.8.4: resolution: {integrity: sha512-DrzLbK9Wol3zeiuZCleD9aUOl0KAaBHR9H6WVVVYPZ4Ya+LYxUFTgSF1jooHcMQCv96Ws96wCaZzIoP3bES8pQ==} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss-load-config@3.1.4: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} @@ -3204,6 +3326,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + turbo@2.10.0: + resolution: {integrity: sha512-o016H9PPtuH2deb3mh3Vci3Avfi9UYgM/RONQisY7HnloupP0IFSbFS3gFYJgFJP8nwBrByHWFQIDa8T2zIXPw==} + hasBin: true + turndown@7.2.2: resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} @@ -3506,12 +3632,12 @@ snapshots: '@atcute/cid@2.4.1': dependencies: - '@atcute/multibase': 1.1.8 + '@atcute/multibase': 1.2.0 '@atcute/uint8array': 1.1.1 '@atcute/client@4.2.1': dependencies: - '@atcute/identity': 1.1.3 + '@atcute/identity': 1.1.4 '@atcute/lexicons': 1.3.0 '@atcute/crypto@2.4.1': @@ -3520,13 +3646,6 @@ snapshots: '@atcute/uint8array': 1.1.1 '@noble/secp256k1': 3.1.0 - '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3)': - dependencies: - '@atcute/identity': 1.1.3 - '@atcute/lexicons': 1.3.0 - '@atcute/util-fetch': 1.0.5 - '@badrap/valita': 0.4.6 - '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.4)': dependencies: '@atcute/identity': 1.1.4 @@ -3534,11 +3653,6 @@ snapshots: '@atcute/util-fetch': 1.0.5 '@badrap/valita': 0.4.6 - '@atcute/identity@1.1.3': - dependencies: - '@atcute/lexicons': 1.3.0 - '@badrap/valita': 0.4.6 - '@atcute/identity@1.1.4': dependencies: '@atcute/lexicons': 1.3.0 @@ -3574,7 +3688,7 @@ snapshots: '@atcute/identity': 1.1.4 '@atcute/lexicons': 1.3.0 '@atcute/uint8array': 1.1.1 - '@atcute/util-text': 1.2.0 + '@atcute/util-text': 1.3.1 '@badrap/valita': 0.4.6 '@atcute/lexicon-resolver@0.1.6(@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.4))(@atcute/identity@1.1.4)': @@ -3601,10 +3715,6 @@ snapshots: '@atcute/cid': 2.4.1 '@atcute/uint8array': 1.1.1 - '@atcute/multibase@1.1.8': - dependencies: - '@atcute/uint8array': 1.1.1 - '@atcute/multibase@1.2.0': dependencies: '@atcute/uint8array': 1.1.1 @@ -3614,19 +3724,19 @@ snapshots: '@atcute/client': 4.2.1 '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.4) '@atcute/lexicons': 1.3.0 - '@atcute/multibase': 1.1.8 + '@atcute/multibase': 1.2.0 '@atcute/oauth-crypto': 0.1.0 '@atcute/oauth-types': 0.1.1 - nanoid: 5.1.6 + nanoid: 5.1.11 transitivePeerDependencies: - '@atcute/identity' '@atcute/oauth-crypto@0.1.0': dependencies: - '@atcute/multibase': 1.1.8 + '@atcute/multibase': 1.2.0 '@atcute/uint8array': 1.1.1 '@badrap/valita': 0.4.6 - nanoid: 5.1.6 + nanoid: 5.1.11 '@atcute/oauth-keyset@0.1.0': dependencies: @@ -3635,19 +3745,19 @@ snapshots: '@atcute/oauth-node-client@1.1.0': dependencies: '@atcute/client': 4.2.1 - '@atcute/identity': 1.1.3 - '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) + '@atcute/identity': 1.1.4 + '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.4) '@atcute/lexicons': 1.3.0 '@atcute/oauth-crypto': 0.1.0 '@atcute/oauth-keyset': 0.1.0 '@atcute/oauth-types': 0.1.1 '@atcute/util-fetch': 1.0.5 '@badrap/valita': 0.4.6 - nanoid: 5.1.6 + nanoid: 5.1.11 '@atcute/oauth-types@0.1.1': dependencies: - '@atcute/identity': 1.1.3 + '@atcute/identity': 1.1.4 '@atcute/lexicons': 1.3.0 '@atcute/oauth-keyset': 0.1.0 '@badrap/valita': 0.4.6 @@ -3679,10 +3789,6 @@ snapshots: dependencies: '@badrap/valita': 0.4.6 - '@atcute/util-text@1.2.0': - dependencies: - unicode-segmenter: 0.14.5 - '@atcute/util-text@1.3.1': dependencies: unicode-segmenter: 0.14.5 @@ -4755,7 +4861,7 @@ snapshots: dependencies: '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) '@tiptap/pm': 3.20.1 - marked: 17.0.4 + marked: 17.0.6 '@tiptap/pm@3.20.1': dependencies: @@ -4810,6 +4916,24 @@ snapshots: '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) '@tiptap/pm': 3.20.1 + '@turbo/darwin-64@2.10.0': + optional: true + + '@turbo/darwin-arm64@2.10.0': + optional: true + + '@turbo/linux-64@2.10.0': + optional: true + + '@turbo/linux-arm64@2.10.0': + optional: true + + '@turbo/windows-64@2.10.0': + optional: true + + '@turbo/windows-arm64@2.10.0': + optional: true + '@tweenjs/tween.js@23.1.3': {} '@tybys/wasm-util@0.10.1': @@ -4851,6 +4975,10 @@ snapshots: dependencies: undici-types: 7.24.6 + '@types/pngjs@6.0.5': + dependencies: + '@types/node': 25.9.1 + '@types/stats.js@0.17.4': {} '@types/supercluster@7.1.3': @@ -5434,6 +5562,11 @@ snapshots: flatted@3.4.1: {} + fractional-indexing@3.4.0: {} + + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -5455,6 +5588,8 @@ snapshots: gsap@3.14.2: {} + he@1.2.0: {} + hex-rgb@4.3.0: {} highlight.js@11.11.1: {} @@ -5713,8 +5848,6 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - marked@17.0.4: {} - marked@17.0.6: {} mdurl@2.0.0: {} @@ -5768,10 +5901,13 @@ snapshots: nanoid@5.1.11: {} - nanoid@5.1.6: {} - natural-compare@1.4.0: {} + node-html-parser@7.1.0: + dependencies: + css-select: 5.2.2 + he: 1.2.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -5849,6 +5985,10 @@ snapshots: picomatch@4.0.3: {} + pixelmatch@6.0.0: + dependencies: + pngjs: 7.0.0 + pkg-types@1.3.1: dependencies: confbox: 0.1.8 @@ -5861,6 +6001,14 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.61.1: {} + + playwright@1.61.1: + dependencies: + playwright-core: 1.61.1 + optionalDependencies: + fsevents: 2.3.2 + plyr@3.8.4: dependencies: core-js: 3.48.0 @@ -5869,6 +6017,8 @@ snapshots: rangetouch: 2.0.1 url-polyfill: 1.1.14 + pngjs@7.0.0: {} + postcss-load-config@3.1.4(postcss@8.5.8): dependencies: lilconfig: 2.1.0 @@ -5911,12 +6061,23 @@ snapshots: prettier: 3.8.1 svelte: 5.53.11 + prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.53.11): + dependencies: + prettier: 3.8.3 + svelte: 5.53.11 + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.1)(svelte@5.53.11))(prettier@3.8.1): dependencies: prettier: 3.8.1 optionalDependencies: prettier-plugin-svelte: 3.5.1(prettier@3.8.1)(svelte@5.53.11) + prettier-plugin-tailwindcss@0.7.2(prettier-plugin-svelte@3.5.1(prettier@3.8.3)(svelte@5.53.11))(prettier@3.8.3): + dependencies: + prettier: 3.8.3 + optionalDependencies: + prettier-plugin-svelte: 3.5.1(prettier@3.8.3)(svelte@5.53.11) + prettier@3.8.1: {} prettier@3.8.3: {} @@ -6411,6 +6572,15 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + turbo@2.10.0: + optionalDependencies: + '@turbo/darwin-64': 2.10.0 + '@turbo/darwin-arm64': 2.10.0 + '@turbo/linux-64': 2.10.0 + '@turbo/linux-arm64': 2.10.0 + '@turbo/windows-64': 2.10.0 + '@turbo/windows-arm64': 2.10.0 + turndown@7.2.2: dependencies: '@mixmark-io/domino': 2.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 96fa1918..2a9ff522 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ +packages: + - "apps/*" + - "packages/*" allowBuilds: core-js: true esbuild: true diff --git a/src/lib/helpers/save.ts b/src/lib/helpers/save.ts deleted file mode 100644 index 45708ac1..00000000 --- a/src/lib/helpers/save.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { Item, WebsiteData, SectionRecord } from '../types'; -import { CardDefinitionsByType } from '../cards'; -import { deleteRecord, putRecord } from '$lib/atproto'; -import { getName, getDescription, getHideProfileSection } from './website'; - -export async function savePage( - data: WebsiteData, - currentItems: Item[], - originalCards: Item[], - originalPublication: string, - originalSections?: SectionRecord[] -) { - const promises = []; - - // Save sections - for (const section of data.sections) { - section.updatedAt = new Date().toISOString(); - section.version = 1; - const record = JSON.parse(JSON.stringify(section)); - const rkey = record.id; - delete record.id; - promises.push( - putRecord({ - collection: 'app.blento.section', - rkey, - record - }) - ); - } - - // Delete removed sections - if (originalSections) { - for (const original of originalSections) { - if (!data.sections.find((s) => s.id === original.id)) { - promises.push(deleteRecord({ collection: 'app.blento.section', rkey: original.id })); - } - } - } - - // Save all current cards. We don't diff against originals because the - // server-side load can modify cards (e.g. fixing overlaps), so the - // "original" the client sees is already the post-fix version — there's - // nothing reliable to diff against. - for (let item of currentItems) { - item.updatedAt = new Date().toISOString(); - // run optional upload function for this card type - const cardDef = CardDefinitionsByType[item.cardType]; - - if (cardDef?.upload) { - item = await cardDef?.upload(item); - } - - const parsedItem = JSON.parse(JSON.stringify(item)); - - parsedItem.page = data.page; - parsedItem.version = 2; - - promises.push( - putRecord({ - collection: 'app.blento.card', - rkey: parsedItem.id, - record: parsedItem - }) - ); - } - - // delete items that are in originalItems but not in items - for (const originalItem of originalCards) { - const item = currentItems.find((i) => i.id === originalItem.id); - if (!item) { - console.log('deleting item', originalItem); - promises.push(deleteRecord({ collection: 'app.blento.card', rkey: originalItem.id })); - } - } - - if ( - data.publication?.preferences?.hideProfile !== undefined && - data.publication?.preferences?.hideProfileSection === undefined - ) { - data.publication.preferences.hideProfileSection = data.publication?.preferences?.hideProfile; - } - - if (!originalPublication || originalPublication !== JSON.stringify(data.publication)) { - data.publication ??= { - name: getName(data), - description: getDescription(data), - preferences: { - hideProfileSection: getHideProfileSection(data) - } - }; - - if (!data.publication.url) { - data.publication.url = 'https://blento.app/' + data.handle; - - if (data.page !== 'blento.self') { - data.publication.url += '/' + data.page.replace('blento.', ''); - } - } - if (data.page !== 'blento.self') { - promises.push( - putRecord({ - collection: 'app.blento.page', - rkey: data.page, - record: data.publication - }) - ); - } else { - promises.push( - putRecord({ - collection: 'site.standard.publication', - rkey: data.page, - record: data.publication - }) - ); - } - - console.log('updating or adding publication', data.publication); - } - - // check if pronouns edited and save - if (data.pronounsRecord?.value?.sets?.length) { - const existing = data.pronounsRecord.value; - const now = new Date().toISOString(); - const record: Record = { - $type: 'app.nearhorizon.actor.pronouns', - sets: existing.sets, - displayMode: existing.displayMode ?? 'all', - createdAt: existing.createdAt ?? now - }; - if (existing.createdAt) { - record.updatedAt = now; - } - promises.push( - putRecord({ - collection: 'app.nearhorizon.actor.pronouns', - rkey: 'self', - record - }) - ); - } - - await Promise.all(promises); - - // Invalidate cached OG image so the next scrape regenerates it. - // Fire-and-forget; server checks auth against the signed-cookie session. - fetch(`/${data.did}/og-new.png`, { method: 'DELETE' }).catch(() => {}); -} diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..dccfdf00 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "declaration": true, + "sourceMap": true + } +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 00000000..ab439ab9 --- /dev/null +++ b/turbo.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**", ".svelte-kit/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "check": { + "dependsOn": ["^build"] + }, + "lint": {}, + "test": { + "dependsOn": ["^build"] + } + } +}