Documentation Index
Fetch the complete documentation index at: https://mintlify.com/covenant-gov/pacto-app/llms.txt
Use this file to discover all available pages before exploring further.
Commons is the leftmost top-nav mode (Commons | DMs | Squads) — Pacto’s public discovery layer. It displays time-bounded broadcasts from public squads and individual users, published as Nostr kind 30078 events on TRUSTED_RELAYS. Unlike DMs or MLS group messages, Commons broadcasts are not encrypted: anyone on those relays can read the tag text and message content. A user is discoverable only while they have an active broadcast; a squad is discoverable only if it has been marked public. The feed refreshes every 60 seconds while Commons is open and shows only broadcasts that have not yet expired.
Broadcast content — including all tags and the message body — is public and visible to anyone subscribed to TRUSTED_RELAYS. Do not include sensitive information in a Commons broadcast. Squad names, tags, and messages are claims only; they are not cryptographically verified beyond the author’s Nostr keypair.
Product rules
| Rule | Behavior |
|---|
| Private squad | Default at creation. No Commons metadata, no Broadcast menu, never appears in the feed. |
| Public squad | Elect 1–3 tags at creation. Automatically publishes a 72-hour #new broadcast on successful creation. Broadcast Squad option in the squad header menu allows re-broadcasting at 24 / 48 / 72-hour durations (no #new tag on re-broadcasts). |
| User visibility | No persistent visibility toggle. A user is discoverable only while an active broadcast exists. |
| New vs active | A user’s first broadcast ever is auto-classified as new_user and carries the reserved #new tag. Every subsequent broadcast is active_user with no #new. Cancelling the first broadcast does not reset this status. Newly created public squads always broadcast #new at creation. The #new tag cannot be self-selected. |
| Cooldown | One active broadcast per (author npub, subject). The UI blocks re-publishing until expiresAt. |
| Cancel | The author can retract an active broadcast. A replacement event with cancelled: true is published (same NIP-33 d tag); clients drop the subject from the feed immediately. The author’s cooldown lifts so they can rebroadcast right away. Newest-event-wins keeps the tombstone authoritative and prevents feed spam. |
| Feed | Shows active (non-expired, non-cancelled) broadcasts only. Relay lookback is ≤ 72 hours. Refreshes every 60 seconds while Commons is open. |
| Squad broadcast roles | All squad members can broadcast in v1. A canBroadcastSquad role gate for Squad Admins is planned for a future release. |
User flows
Discover
- Open Commons from the top navigation bar.
- Passive browse — scroll the curated category tiles. Click a tile to expand its tags and filter the feed to any broadcast matching that category.
- Focused search — open Browse tags to drill into the genre-style dropdown. Selecting tags from this menu hides the category tiles and filters by the AND of up to 3 chosen tags (each shown as a removable chip in the filter bar). Category tile searches match ANY tag in that category with no 3-tag cap.
- Refine results with the Show (all / squads / users) and Audience (all / new / active) segmented switches. Picking tags from the Tags menu clears an active category; opening Categories resets all filters.
- Cards display: name, tags, message, and time until expiry.
- Message (user card) → opens a DM thread with that npub.
- Request to join (squad card) → sends a structured join-request DM to the squad (see Join requests below).
Publish a squad broadcast
- Create a public squad → the app automatically publishes a 72-hour
#new broadcast using the tags chosen during creation.
- Squad header menu → Broadcast Squad (public squads only) → duration + message modal. No
#new tag. Blocked by cooldown copy if a broadcast is still active.
Publish a user broadcast
- Open Commons → click + Broadcast in the personal panel.
- Choose up to 3 tags from the curated tag picker (tags are pre-defined; no free-text entry).
- Select a duration and write an optional message.
- Submit → publishes a kind 30078 event. The first broadcast ever is auto-tagged
#new; later broadcasts are not.
Commons broadcasts are published as NIP-33 parameterized replaceable events (kind 30078). The same kind is used for other app-specific public events (e.g. dashboard polls), but Commons events use their own d tag and JSON schema.
| Field | Value |
|---|
kind | 30078 |
content | JSON string — schema pacto.commons.broadcast.v1 |
d | pacto_commons_broadcast (v1 flat key; per-slot d tags planned post-v1) |
client | pacto |
subject | user | squad |
exp | Expiry unix timestamp as a string |
t | One tag per tag value (lowercase; max 3 for user and squad re-broadcasts) |
squad | Announcements MLS group id (present only when subject=squad) |
content may also include cancelled: true as a tombstone to retract a still-unexpired broadcast. Clients keep the newest event per (authorNpub, subjectId) and remove the subject from the feed when that newest event is cancelled.
JSON payload examples
Squad broadcast:
{
"schema": "pacto.commons.broadcast.v1",
"subject": "squad",
"message": "Open cohort for on-chain gov experiments.",
"durationHours": 24,
"expiresAt": 1740000000,
"tags": ["neo", "coordination"],
"squad": {
"id": "<announcements_mls_group_id>",
"name": "Neo Builders",
"kind": "squad",
"iconUrl": "https://…"
}
}
User broadcast:
{
"schema": "pacto.commons.broadcast.v1",
"subject": "user",
"message": "Building wallet tooling; happy to DM.",
"durationHours": 48,
"expiresAt": 1740086400,
"tags": ["neo"],
"audience": "active_user"
}
audience is new_user | active_user for user subjects only. It is omitted from squad broadcasts.
Validation rules
Both publisher and consumer enforce these constraints. Events that fail validation are silently dropped from the feed.
| Rule | Detail |
|---|
| Tags | 1–3 tags per broadcast; each matches ^[a-z0-9_]{1,32}$ after lowercasing and stripping # |
| TTL | durationHours must be one of: 24, 48, 72, 168, 336, 720 (hours; 168 = 7 days, 336 = 14 days, 720 = 30 days) |
| Expiry | expiresAt ≈ created_at + durationHours × 3600 (±60 seconds tolerance) |
| Squad id | When subject=squad, the squad tag value must match squad.id in the content JSON (the stable announcements MLS group id) |
| Expired events | Dropped from the feed and pruned from the local cache |
Local persistence
| Key / table | Purpose |
|---|
SQLite squads | Squad catalog with visibility and commons_tags columns — squad’s public status and tag choices |
SQLite commons_broadcasts | Ingested feed cache per account database |
pacto_commons_broadcasted_<npub> | Set after first user broadcast; marks all future user broadcasts as active_user. Persists across logout so a returning account is not re-classified as new. |
pacto_commons_broadcasts_<npub> | Own active broadcasts — used for cooldown UX in the UI |
pacto_commons_join_requests_<npub> | Join-request rate-limit tracker |
pacto_commons_broadcasts_<npub> and pacto_commons_join_requests_<npub> are cleared on logout via clearAccountState. pacto_commons_broadcasted_<npub> is intentionally kept across logouts.
Tauri commands reference
| Command | Purpose |
|---|
commons_publish_broadcast | Publish a new broadcast (or tombstone) to TRUSTED_RELAYS |
commons_fetch_broadcasts | Fetch active kind 30078 events from TRUSTED_RELAYS and update the local cache |
commons_get_local_active | Return active (non-expired, non-cancelled) broadcasts from the local SQLite cache |
commons_cancel_broadcast | Publish a cancelled: true replacement event and clear the local cooldown |
Implementation lives in src-tauri/src/commons.rs. Frontend API wrappers are in src/lib/api/commons.ts.
Join requests
Clicking Request to join on a squad card sends a kind 14 DM (plain Nostr text rumor inside a Gift Wrap) with a structured JSON body to the squad’s broadcast author pubkey:
{
"schema": "pacto.commons.join_request.v1",
"squadId": "<announcements_mls_group_id>",
"message": "Would love to join!"
}
The join request is rate-limited client-side: one request per squad per 24 hours, tracked in pacto_commons_join_requests_<npub>. The squad side handles the request through the existing invite flow — receiving the structured DM and optionally issuing an MLS Welcome.
Privacy and abuse notes
- Public by design — tags and broadcast text are on-relay and readable by anyone subscribing to TRUSTED_RELAYS. Broadcast composition modals make this explicit to users.
- Impersonation — squad name, id, and icon in the broadcast JSON are claims by the author. The join flow uses MLS cryptography for actual membership, not card text.
- Spam — Commons traffic is restricted to TRUSTED_RELAYS only. Mute and report flows are not in v1.
- Tag charset — ASCII lowercase letters, digits, and underscores only (
^[a-z0-9_]{1,32}$). The reserved #new tag is managed by the app and cannot be self-selected.