Skip to content
15 changes: 15 additions & 0 deletions .changeset/add-collection-avatar-banner-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@hypercerts-org/sdk-core": minor
---

Add comprehensive documentation and tests for collection avatar and banner images

Collections and projects now support avatar (thumbnail/icon) and banner (header/cover) images. Images can be provided as
Blobs for upload or as URI strings for external references.

**What's Included:**

- Comprehensive JSDoc documentation for `HypercertCollection` type explaining avatar and banner usage
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changeset claims "Comprehensive JSDoc documentation for HypercertCollection type explaining avatar and banner usage" but the HypercertCollection type at line 152 has no JSDoc documentation - it's just a bare type alias. The avatar/banner documentation exists only in the CreateCollectionParams type comment (line 319). Consider either adding JSDoc documentation to the HypercertCollection type itself, or updating the changeset description to accurately reflect where the documentation was added (e.g., "Comprehensive JSDoc documentation in CreateCollectionParams explaining avatar and banner usage").

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Suggest a change

- Tests for creating collections with avatar/banner using both Blobs and URI strings
- Tests for updating collection images (add, update, remove, preserve)
- Examples showing avatar/banner in collections and projects
12 changes: 12 additions & 0 deletions .changeset/add-collection-item-weight-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@hypercerts-org/sdk-core": patch
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changeset is marked as "patch" but describes adding documentation and tests for a new feature (collection item weights). According to semantic versioning, a "patch" bump should be for bug fixes or trivial documentation-only changes. Since this changeset includes:

  • Comprehensive test coverage for the new feature (tests at lines 990-1072)
  • Documentation for new functionality
  • Examples showing how to use the feature

It should be categorized as "minor" instead of "patch", as it's documenting a new feature that's now available to users. If this were truly documentation-only without any functionality change, "patch" might be appropriate, but the tests indicate this is exposing new functionality.

Suggested change
"@hypercerts-org/sdk-core": patch
"@hypercerts-org/sdk-core": minor

Copilot uses AI. Check for mistakes.
---

Add documentation for collection item weights (lexicon v0.10.0-beta.7)

Collections now support optional weights on items for proportional attribution. Each item in a collection's `items`
array can have an `itemWeight` field (positive number as string) to indicate relative weighting.

- Enhanced documentation for `HypercertCollectionItem` type with usage examples
- Added examples showing weighted items, nested collections, and basic items
- Documented `CollectionItemInput` helper type for SDK operations
13 changes: 13 additions & 0 deletions .changeset/add-rich-text-facets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@hypercerts-org/sdk-core": minor
---

Add rich text facet support for activity descriptions (lexicon v0.10.0-beta.7)

Activities now support rich text annotations (facets) in their descriptions, enabling mentions (@user), URLs, hashtags
(#tag), and other inline markup.

- Added `shortDescriptionFacets` and `descriptionFacets` fields to `CreateHypercertParams`
- Updated `create()` method to include facet fields in hypercert records
- Enhanced `HypercertClaim` documentation with comprehensive facet examples
- Added examples showing mentions, links, and tag facets with proper byte indexing
43 changes: 24 additions & 19 deletions .claude/skills/sync-lexicons/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ Example plan structure:

**SDK Tasks**:

- [ ] Add/update documentation for X
- [ ] Add type exports for Y
- [ ] Add usage examples
- [ ] Add/update JSDoc documentation to methods (e.g., createX(), updateX())
- [ ] Add type exports for Y if needed
- [ ] Add usage examples in method documentation
- [ ] Add/update tests
- [ ] Build and test
- [ ] Create changeset (minor/major - reason)

Expand Down Expand Up @@ -217,15 +218,18 @@ Present this plan to the user and ask:

For the current change (e.g., "Change 1: Collection Item Weights"):

1. **Update documentation** in `packages/sdk-core/src/services/hypercerts/types.ts`:
- Add JSDoc comments for new/changed types
- Add usage examples
1. **Update method documentation** - Focus on documenting the methods users will call:
- Add/update JSDoc comments on SDK methods (e.g., `createCollection()`, `updateCollection()`)
- Add usage examples in method documentation showing new features
- Document parameters, return values, and behavior
- Document breaking changes if any
- Location: `packages/sdk-core/src/repository/HypercertOperationsImpl.ts` and interfaces

2. **Add type exports** if needed:
- Add type aliases for new lexicon types
- Add helper types for SDK operations
- Add type aliases for new lexicon types in `packages/sdk-core/src/services/hypercerts/types.ts`
- Add helper types for SDK operations (CreateParams, UpdateParams, etc.)
- Export from appropriate modules
- Keep type documentation minimal - let method docs do the heavy lifting

3. **Update SDK code** if needed:
- Modify operations to support new fields
Expand All @@ -248,7 +252,7 @@ For the current change (e.g., "Change 1: Collection Item Weights"):
pnpm changeset
```

- Follow guidance from `writing-changesets` skill
- Use the process in the `writing-changesets` skill
- Reference the specific feature being added
- Use appropriate bump type

Expand Down Expand Up @@ -305,20 +309,15 @@ After ALL changes are implemented:

### Don't Hardcode Lexicon Names

- ❌ `if (lexicon === "HYPERCERTS_LEXICONS") ...`
- ❌ `if (lexicon === "HYPERCERTS_LEXICON") ...`
- ✅ Read dynamically from the lexicons package exports

### Don't Skip the CHANGELOG
### Don't Skip Review of the CHANGELOG.md review or diffs

- ❌ Immediately update without reviewing changes
- ✅ Always check `CHANGELOG.md` first to see exactly what changed between versions
- ✅ Then run `git diff` for additional context

### Don't Skip the Diff Step

- ❌ Immediately update without reviewing changes
- ✅ Always run `git diff` to see what actually changed

### Don't Forget to Rebuild

- ❌ Update imports and ship
Expand All @@ -330,9 +329,15 @@ The sync process focuses on making SDK changes visible and usable to developers.

### Documentation Updates

1. **Type documentation** - JSDoc comments explaining new features
2. **Usage examples** - Code snippets showing how to use new fields/types
3. **Breaking change notes** - Clear warnings about incompatible changes
**IMPORTANT**: Document methods, not types. Users call methods, not types.

1. **Method documentation** - JSDoc comments on SDK methods explaining new features
- Focus on `createX()`, `updateX()`, `getX()` methods in `HypercertOperationsImpl.ts`
- Document parameters, return values, and behavior
- Add `@example` tags showing how to use new fields
2. **Usage examples** - Code snippets in method docs showing how to use new fields/types
3. **Breaking change notes** - Clear warnings in method docs about incompatible changes
4. **Type documentation** - Keep minimal; types should be self-explanatory from method docs

### Type System Updates

Expand Down
8 changes: 8 additions & 0 deletions packages/sdk-core/src/repository/HypercertOperationsImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ export class HypercertOperationsImpl extends EventEmitter<HypercertEvents> imple
hypercertRecord.image = imageBlobRef;
}

if (params.shortDescriptionFacets) {
hypercertRecord.shortDescriptionFacets = params.shortDescriptionFacets;
}

if (params.descriptionFacets) {
hypercertRecord.descriptionFacets = params.descriptionFacets;
}

Comment on lines +286 to +293
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve empty facet arrays.

Line 286 and Line 290 use truthy checks, which drop empty arrays; that loses an explicit “no facets” intent. Prefer an explicit !== undefined check.

🐛 Proposed fix
-    if (params.shortDescriptionFacets) {
+    if (params.shortDescriptionFacets !== undefined) {
       hypercertRecord.shortDescriptionFacets = params.shortDescriptionFacets;
     }

-    if (params.descriptionFacets) {
+    if (params.descriptionFacets !== undefined) {
       hypercertRecord.descriptionFacets = params.descriptionFacets;
     }
🤖 Prompt for AI Agents
In `@packages/sdk-core/src/repository/HypercertOperationsImpl.ts` around lines 286
- 293, The current truthy checks in HypercertOperationsImpl that set
hypercertRecord.shortDescriptionFacets and hypercertRecord.descriptionFacets
drop empty arrays; change the guards to explicit undefined checks (e.g., if
(params.shortDescriptionFacets !== undefined) and if (params.descriptionFacets
!== undefined)) so empty arrays are preserved as intentional values when
assigning to hypercertRecord.shortDescriptionFacets and
hypercertRecord.descriptionFacets.

const hypercertValidation = validate(hypercertRecord, HYPERCERT_COLLECTIONS.CLAIM, "main", false);
if (!hypercertValidation.success) {
throw new ValidationError(`Invalid hypercert record: ${hypercertValidation.error?.message}`);
Expand Down
37 changes: 37 additions & 0 deletions packages/sdk-core/src/repository/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @packageDocumentation
*/

import type { AppBskyRichtextFacet } from "@atproto/api";
import type { EventEmitter } from "eventemitter3";
import type {
LocationParams,
Expand Down Expand Up @@ -180,6 +181,42 @@ export interface CreateHypercertParams {
*/
shortDescription: string;

/**
* Optional rich text facets for the short description (v0.10.0-beta.7+).
*
* Enables mentions (@user), URLs, hashtags (#tag), and other inline annotations
* in the short description text. Each facet specifies a byte range and feature type.
*
* @example
* ```typescript
* shortDescriptionFacets: [
* {
* index: { byteStart: 13, byteEnd: 19 }, // "@alice"
* features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:alice123" }]
* }
* ]
* ```
*/
shortDescriptionFacets?: AppBskyRichtextFacet.Main[];

/**
* Optional rich text facets for the full description (v0.10.0-beta.7+).
*
* Enables mentions (@user), URLs, hashtags (#tag), and other inline annotations
* in the description text. Each facet specifies a byte range and feature type.
*
* @example
* ```typescript
* descriptionFacets: [
* {
* index: { byteStart: 6, byteEnd: 33 }, // URL
* features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.com" }]
* }
* ]
* ```
*/
descriptionFacets?: AppBskyRichtextFacet.Main[];

/**
* Optional cover image for the hypercert.
*
Expand Down
128 changes: 127 additions & 1 deletion packages/sdk-core/src/services/hypercerts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,69 @@ import type {
// ============================================================================

export type StrongRef = ComAtprotoRepoStrongRef.Main;

/**
* Hypercert claim (activity) record.
*
* Represents a single hypercert activity with metadata including descriptions,
* time periods, work scope, contributors, and optional rich text annotations.
*
* @remarks
* **Rich Text Facets (beta.7+):**
* - `shortDescriptionFacets` - Annotations for the short description text
* - `descriptionFacets` - Annotations for the full description text
*
* Facets enable rich text features like mentions (@user), URLs, hashtags (#tag),
* and other inline annotations. Each facet specifies:
* - `index`: Byte range in the UTF-8 encoded text (byteStart, byteEnd)
* - `features`: Array of feature objects (mention, link, tag, etc.)
*
* @example Basic claim without facets
* ```typescript
* const claim: HypercertClaim = {
* $type: "org.hypercerts.claim.activity",
* createdAt: new Date().toISOString(),
* shortDescription: "Community cleanup project",
* description: "Monthly beach cleanup initiative",
* startDate: "2024-01-01T00:00:00Z",
* endDate: "2024-12-31T23:59:59Z",
* // ... other fields
* };
* ```
*
* @example Claim with rich text facets
* ```typescript
* const claimWithFacets: HypercertClaim = {
* $type: "org.hypercerts.claim.activity",
* createdAt: new Date().toISOString(),
* shortDescription: "Organized by @alice for #sustainability",
* shortDescriptionFacets: [
* {
* index: { byteStart: 13, byteEnd: 19 }, // "@alice"
* features: [{ $type: "app.bsky.richtext.facet#mention", did: "did:plc:alice123" }]
* },
* {
* index: { byteStart: 24, byteEnd: 39 }, // "#sustainability"
* features: [{ $type: "app.bsky.richtext.facet#tag", tag: "sustainability" }]
* }
* ],
* description: "Visit https://example.com/cleanup for more info",
* descriptionFacets: [
* {
* index: { byteStart: 6, byteEnd: 33 }, // URL
* features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.com/cleanup" }]
* }
* ],
* startDate: "2024-01-01T00:00:00Z",
* endDate: "2024-12-31T23:59:59Z",
* // ... other fields
* };
* ```
*
* @see {@link https://atproto.com/specs/richtext#facets|AT Protocol Rich Text Facets}
*/
export type HypercertClaim = OrgHypercertsClaimActivity.Main;

export type HypercertRights = OrgHypercertsClaimRights.Main;
export type HypercertContributionDetails = OrgHypercertsClaimContributionDetails.Main;
export type HypercertContributorInformation = OrgHypercertsClaimContributorInformation.Main;
Expand All @@ -88,7 +150,51 @@ export type HypercertMeasurement = OrgHypercertsClaimMeasurement.Main;
export type HypercertEvaluation = OrgHypercertsClaimEvaluation.Main;
export type HypercertEvidence = OrgHypercertsClaimEvidence.Main;
export type HypercertCollection = OrgHypercertsClaimCollection.Main;
/** Collection item with optional weight */

/**
* Collection item with optional weight.
*
* Represents a single item in a collection's `items` array. Each item can reference
* either an activity or another collection (nested collections), with an optional
* weight for proportional attribution.
*
* @remarks
* Structure (beta.7+):
* - `itemIdentifier` (required): StrongRef to the item (activity or collection)
* - `itemWeight` (optional): Positive numeric value as string for proportional weighting
*
* @example Basic item without weight
* ```typescript
* const item: HypercertCollectionItem = {
* itemIdentifier: {
* uri: "at://did:plc:abc123/org.hypercerts.claim.activity/xyz789",
* cid: "bafyreiabc123..."
* }
* };
* ```
*
* @example Item with weight for proportional attribution
* ```typescript
* const weightedItem: HypercertCollectionItem = {
* itemIdentifier: {
* uri: "at://did:plc:abc123/org.hypercerts.claim.activity/xyz789",
* cid: "bafyreiabc123..."
* },
* itemWeight: "2.5" // This activity has 2.5x weight compared to items with weight "1"
* };
* ```
*
* @example Nested collection
* ```typescript
* const nestedCollection: HypercertCollectionItem = {
* itemIdentifier: {
* uri: "at://did:plc:abc123/org.hypercerts.claim.collection/sub789",
* cid: "bafyreiabc456..."
* },
* itemWeight: "1.0"
* };
* ```
*/
export type HypercertCollectionItem = OrgHypercertsClaimCollection.Item;
/** Work scope tag for creating reusable scope atoms */
export type HypercertWorkScopeTag = OrgHypercertsHelperWorkScopeTag.Main;
Expand Down Expand Up @@ -144,6 +250,26 @@ export type { OrgHypercertsClaimCollection as CollectionLexicon } from "@hyperce
// SDK Input Helper Types (Derived from Lexicon)
// ============================================================================

/**
* Input type for collection items.
*
* Same as {@link HypercertCollectionItem} but with `$type` field optional since
* the SDK will automatically populate it when creating records.
*
* @example
* ```typescript
* const items: CollectionItemInput[] = [
* {
* itemIdentifier: { uri: "at://did:plc:abc/org.hypercerts.claim.activity/123", cid: "bafyrei..." },
* itemWeight: "1.0"
* },
* {
* itemIdentifier: { uri: "at://did:plc:abc/org.hypercerts.claim.activity/456", cid: "bafyrei..." },
* itemWeight: "2.5" // This activity weighted 2.5x more
* }
* ];
* ```
*/
export type CollectionItemInput = SetOptional<OrgHypercertsClaimCollection.Item, "$type">;

/**
Expand Down
Loading