diff --git a/.changeset/add-project-sidecar.md b/.changeset/add-project-sidecar.md new file mode 100644 index 00000000..e3f239a9 --- /dev/null +++ b/.changeset/add-project-sidecar.md @@ -0,0 +1,5 @@ +--- +"@hypercerts-org/lexicon": minor +--- + +Remove org.hypercerts.claim.project lexicon and replace with org.hypercerts.claim.collection.project sidecar. Projects are now represented as collections with an optional project sidecar (same TID) that provides rich-text descriptions, avatars, and cover photos. Avatar and coverPhoto fields moved from base collection to project sidecar. Collections without the project sidecar are simple groupings; collections with it are "projects" with rich documentation. diff --git a/.changeset/refactor-collection-items-array.md b/.changeset/refactor-collection-items-array.md new file mode 100644 index 00000000..65ced531 --- /dev/null +++ b/.changeset/refactor-collection-items-array.md @@ -0,0 +1,5 @@ +--- +"@hypercerts-org/lexicon": minor +--- + +Refactor collection lexicon to use items array instead of activities. The items array contains plain strongRefs (com.atproto.repo.strongRef) that can reference activities (org.hypercerts.claim.activity) and/or other collections (org.hypercerts.claim.collection), enabling recursive collection nesting. This change removes the activityWeight object structure from the base collection lexicon. diff --git a/ERD.puml b/ERD.puml index 885616a6..a5bdbc10 100644 --- a/ERD.puml +++ b/ERD.puml @@ -182,14 +182,22 @@ dataclass rights { ' by creating a location record with the same TID dataclass collection { !if (SHOW_FIELDS == "true") - type - title - shortDescription? - description? + collectionTitle + shortCollectionDescription? + collectionDescription? + items[] + createdAt + !endif +} + +' org.hypercerts.claim.collection.project (sidecar) +dataclass collectionProject { + !if (SHOW_FIELDS == "true") + projectTitle? + shortProjectDescription? + projectDescription (Leaflet ref) avatar? coverPhoto? - activities[] - location? createdAt !endif } @@ -285,11 +293,9 @@ hiddenLocation2 -[hidden]-> location measurement --> location !endif -collection::activities --> activity -collection ..|> location : "sidecar\n(same TID)" - -project::activities --> activity -project::location --> location +collection::items --> activity +collection::items --> collection : "recursive\nnesting" +collection ..|> collectionProject : "sidecar\n(same TID)" activity::contributions -l--> contribution activity::rights --> rights diff --git a/README.md b/README.md index 3d976c7e..b8011877 100644 --- a/README.md +++ b/README.md @@ -405,20 +405,19 @@ Hypercerts-specific lexicons for tracking impact work and claims. **Lexicon ID:** `org.hypercerts.claim.collection` -**Description:** A collection/group of items (activities and/or other collections). Collections support recursive nesting. Use `org.hypercerts.claim.collection.project` as a sidecar (same TID) for project-specific metadata. +**Description:** A collection/group of items (activities and/or other collections). Collections support recursive nesting, allowing collections to contain other collections. Use app.certified.location as a sidecar (same TID) for location metadata. **Key:** `tid` #### Properties -| Property | Type | Required | Description | Comments | -| ---------------------------- | -------- | -------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `collectionTitle` | `string` | ✅ | The title of this collection | | -| `shortCollectionDescription` | `string` | ❌ | Short summary of this collection, suitable for previews and list views | | -| `collectionDescription` | `string` | ❌ | Full description of this collection, suitable for detail views | | -| `items` | `array` | ✅ | Array of strong references to items in this collection | Items can be activities (`org.hypercerts.claim.activity`) and/or other collections (`org.hypercerts.claim.collection`). Enables recursive nesting. | -| `location` | `ref` | ❌ | A strong reference to a location record describing where the work took place | The referenced record must conform to the `app.certified.location` lexicon. | -| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | +| Property | Type | Required | Description | Comments | +| ---------------------------- | -------- | -------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `collectionTitle` | `string` | ✅ | The title of this collection | maxLength: 800, maxGraphemes: 80 | +| `shortCollectionDescription` | `string` | ❌ | Short summary of this collection, suitable for previews and list views | maxLength: 3000, maxGraphemes: 300 | +| `collectionDescription` | `string` | ❌ | Full description of this collection, suitable for detail views | maxLength: 3000, maxGraphemes: 300 | +| `items` | `array` | ✅ | Array of strong references to items in this collection | Items can be activities (`org.hypercerts.claim.activity`) and/or other collections (`org.hypercerts.claim.collection`). Enables recursive nesting. | +| `createdAt` | `string` | ✅ | Client-declared timestamp when this record was originally created | | #### Example: Creating a Collection with Nested Items @@ -428,6 +427,8 @@ import { TID } from "@atproto/common"; const collectionRecord = { $type: "org.hypercerts.claim.collection", collectionTitle: "Climate Action Projects", + shortCollectionDescription: + "A collection of climate-related activities and sub-collections", items: [ // Reference to an activity { @@ -463,7 +464,7 @@ const collectionRecord = { | Property | Type | Required | Description | Comments | | ------------------------- | -------- | -------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `projectTitle` | `string` | ❌ | The title of this project | maxLength: 800, maxGraphemes: 80 | +| `projectTitle` | `string` | ❌ | The title of this collection | maxLength: 800, maxGraphemes: 80 | | `shortProjectDescription` | `string` | ❌ | Short summary of this project, suitable for previews and list views | maxLength: 3000, maxGraphemes: 300 | | `projectDescription` | `ref` | ✅ | Rich-text description of this project, represented as a Leaflet linear document | References must conform to `pub.leaflet.pages.linearDocument#main` | | `avatar` | `blob` | ❌ | Primary avatar image representing this project across apps and views | Typically a square logo or project identity image; image/png or image/jpeg, maxSize: 1000000 | diff --git a/lexicons/org/hypercerts/claim/collection.json b/lexicons/org/hypercerts/claim/collection.json index 7981bbf4..66302da0 100644 --- a/lexicons/org/hypercerts/claim/collection.json +++ b/lexicons/org/hypercerts/claim/collection.json @@ -4,51 +4,36 @@ "defs": { "main": { "type": "record", - "description": "A group of hypercerts that have a specific property. Can be a project or a collection.", + "description": "A collection/group of items (activities and/or other collections). Collections support recursive nesting. Use app.certified.location as a sidecar (same TID) for location metadata.", "key": "tid", "record": { "type": "object", - "required": ["type", "title", "createdAt"], + "required": ["collectionTitle", "items", "createdAt"], "properties": { - "type": { - "type": "string", - "description": "The type of this group. Can be 'project', 'collection', or any other custom type string." - }, - "title": { + "collectionTitle": { "type": "string", "description": "The title of this collection", "maxLength": 800, "maxGraphemes": 80 }, - "shortDescription": { + "shortCollectionDescription": { "type": "string", "maxLength": 3000, "maxGraphemes": 300, - "description": "A short description of this collection" - }, - "description": { - "type": "ref", - "ref": "pub.leaflet.pages.linearDocument#main", - "description": "Rich-text description, represented as a Leaflet linear document." - }, - "avatar": { - "type": "blob", - "description": "Primary avatar image representing this collection across apps and views; typically a square image.", - "accept": ["image/png", "image/jpeg"], - "maxSize": 1000000 + "description": "Short summary of this collection, suitable for previews and list views" }, - "coverPhoto": { - "type": "blob", - "description": "The cover photo of this collection.", - "accept": ["image/png", "image/jpeg"], - "maxSize": 1000000 + "collectionDescription": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300, + "description": "Full description of this collection, suitable for detail views" }, - "activities": { + "items": { "type": "array", - "description": "Array of activities with their associated weights in this collection", + "description": "Array of strong references to items in this collection. Items can be activities (org.hypercerts.claim.activity) and/or other collections (org.hypercerts.claim.collection).", "items": { "type": "ref", - "ref": "org.hypercerts.claim.activity#activityWeight" + "ref": "com.atproto.repo.strongRef" } }, "createdAt": { diff --git a/lexicons/org/hypercerts/claim/collection/project.json b/lexicons/org/hypercerts/claim/collection/project.json new file mode 100644 index 00000000..a1385694 --- /dev/null +++ b/lexicons/org/hypercerts/claim/collection/project.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "org.hypercerts.claim.collection.project", + "defs": { + "main": { + "type": "record", + "description": "Project-specific metadata for a collection. Uses the sidecar pattern with the same record key (TID) as the collection record. Provides rich-text description capabilities for project-type collections.", + "key": "tid", + "record": { + "type": "object", + "required": ["projectDescription", "createdAt"], + "properties": { + "projectTitle": { + "type": "string", + "description": "The title of this collection", + "maxLength": 800, + "maxGraphemes": 80 + }, + "shortProjectDescription": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300, + "description": "Short summary of this project, suitable for previews and list views" + }, + "projectDescription": { + "type": "ref", + "ref": "pub.leaflet.pages.linearDocument#main", + "description": "Rich-text description of this project, represented as a Leaflet linear document." + }, + "avatar": { + "type": "blob", + "description": "Primary avatar image representing this project across apps and views; typically a square logo or project identity image.", + "accept": ["image/png", "image/jpeg"], + "maxSize": 1000000 + }, + "coverPhoto": { + "type": "blob", + "description": "The cover photo of this project.", + "accept": ["image/png", "image/jpeg"], + "maxSize": 1000000 + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "Client-declared timestamp when this project metadata was created" + } + } + } + } + } +}