Overhaul AuthDialog's login step:
- Add translation keys for every previously-hardcoded string (titles,
buttons, placeholders, status labels, validation/errors) across all
sixteen locales.
- The secret-key form is no longer collapsible — it's always open and is
the first option, followed by 'Log in with extension', then a
text-with-arrow link to the remote-signer step.
- Move the key-file upload icon onto the same row as the nsec input
(instead of beside the submit button); submit button is now full-width
below.
- Surface extension-login errors as a destructive toast rather than
writing them into the nsec input's inline error.
- Replace the centered 'Back' text buttons with a back arrow in the
top-left of the dialog header.
Also correct AGENTS.md's i18n section: the project ships fifteen
non-English locales (hi, id, sw, tr, zh-Hant were missing from the list).
Agora is a P2P crowdfunding client using on-chain Bitcoin with a
non-custodial HD wallet (BIP-86 Taproot, BIP-352 silent payments),
not a Lightning project. Update the overview, document the on-chain
wallet and crowdfunding hooks, and demote Lightning to secondary
tipping. Drop the stale useShakespeare hook reference.
Adds an Internationalization section to AGENTS.md spelling out that
edits to user-facing strings must propagate across every locale in the
same change, not just en.json. Lists the ten translated locales (ar,
es, fa, fr, km, ps, pt, ru, sn, zh), gives rules for edits / new keys /
removals, notes that locales.test.ts only catches the structural
direction (extra keys in a locale) — missing keys silently fall back
to English — and recommends parallelizing the per-language work with
subagents for one-string-across-ten-locales edits.
The default behavior of waiting for an explicit commit request leads
to leaving the working tree dirty between turns. Agora's expectation
is the opposite — finish a task, commit the result, let the user
decide when to push.
fastlane's deliver action invokes Apple's iTMSTransporter / altool to
push the IPA to App Store Connect, and those tools only ship inside
Xcode. On a generic ruby:3.3 Linux container the upload step crashed
with 'No such file or directory @ dir_chdir0' from
JavaTransporterExecutor#execute, because Helper.itms_path resolved
to a missing Xcode path.
Move publish-app-store onto the same self-hosted Mac runner as
build-ipa (tags: [macos]), drop the now-unnecessary 'gem install
fastlane' (the Mac has it on PATH via ~/.bash_profile), and unset
APP_STORE_CONNECT_API_KEY_PATH to mirror build-ipa's defense against
fastlane's env-var collision (match expects a JSON descriptor there;
we pass the API key inline via the Fastfile).
Update AGENTS.md and the release / ci-cd-publishing / mac-runner
skills, which all incorrectly described publish-app-store as a
Linux-only API call.
Regression-of: b8773c47
Each CHANGELOG.md release section now begins with a single plaintext
paragraph (max ~500 chars) before any `### Category` heading. That
paragraph drives the release blurb in three storefronts and the
in-app version-update toast, so we no longer ship a marketing-grade
description in one place and a raw bullet list in another.
scripts/extract-release-notes.mjs is the single source of truth for
extraction. It emits the full section (summary + lists) by default
and only the summary paragraph with --summary, with a
`Ditto vX.Y.Z` fallback for legacy entries that have no summary.
CI changes:
- New `release-notes` job (build stage, default node:22 image)
produces `artifacts/release-notes.md` and
`artifacts/release-notes-summary.txt` once per pipeline.
- `release` job pulls release-notes.md as the GitLab Release
description (replaces the old inline awk extraction). It now uses
`needs:` with `artifacts: false` for build-apk/build-ipa to
avoid re-downloading the .apk/.aab/.ipa it doesn't open.
- `publish-app-store` copies release-notes-summary.txt to
`ios/fastlane/metadata/en-US/release_notes.txt` (replaces its
own awk extraction).
- `publish-google-play` drops `--skip_upload_changelogs`, writes
the summary to
`android/fastlane/metadata/android/en-US/changelogs/<versionCode>.txt`
and points fastlane supply at `--metadata_path`. This is the
first time we upload a What's New text to the Play Store from CI.
App-side changes:
- `src/lib/changelog.ts` parser captures the leading non-blank
paragraph (before any bullet or category heading) into
`entry.summary`.
- `VersionCheck.tsx` toast uses `entry.summary` when present,
falling back to the legacy 60-char first-bullet excerpt for
backward compatibility.
- `ChangelogPage` renders the summary as a lede paragraph above
the bullet list in both LatestRelease and ChangelogEntryCard.
Changelog content:
- Added summary paragraphs to v2.14.3, v2.14.2, v2.14.1.
Skill + AGENTS.md updates:
- `release` skill documents the summary paragraph format, the
500-char convention, and the seven-job pipeline.
- `ci-cd-publishing` skill gains a 'Release notes pipeline' section
mapping each storefront to its source artifact.
- AGENTS.md pipeline summary mentions release-notes and the summary
flow into both store "What's new" fields.
Mirror the existing Android publishing flow for iOS. The pipeline
gains two jobs: build-ipa runs on a self-hosted Mac runner and
produces a signed App Store IPA; publish-app-store runs on a shared
Linux runner and submits the prebuilt IPA to App Store Connect.
Build pipeline (.gitlab-ci.yml):
- build-ipa (Mac, stage build, parallel with build-apk): decodes the
ASC API key, runs match (with api_key, so cert validity is verified
against Apple before xcodebuild starts), builds web assets, syncs
Capacitor, stamps MARKETING_VERSION. Uploads Ditto-${CI_COMMIT_TAG}
.ipa to GitLab's Generic Packages registry.
- publish-app-store (Linux ruby:3.3, needs: [build-ipa]): gem
install fastlane, decode the ASC API key, extract the changelog
section into release_notes.txt, fastlane submit_release with
IPA_PATH pointing at the inherited artifact. No Xcode, no signing,
no keychain \u2014 pure Apple API call.
- release job now needs both build-apk and build-ipa, and links three
assets (APK / AAB / IPA).
fastlane (ios/fastlane/Fastfile, Matchfile, Appfile, metadata/):
- Four lanes: build_ipa (CI build), submit_release (CI publish, reads
IPA_PATH from env), release (single-step convenience for local
dev), submit_only (debug lane to re-submit an already-uploaded
build).
- Match config points at the private gitlab.com/soapbox-pub
/certificates repo. App Store Connect API key is built inline in
the Fastfile to avoid a collision with match's APP_STORE_CONNECT
_API_KEY_PATH env var (match wants a JSON descriptor, the action
writes a raw .p8). CI overrides CODE_SIGN_STYLE=Manual via xcargs
so the Xcode project can stay on Automatic for local development.
Vite config (vite.config.ts):
- Renames the build-time config override env var from CONFIG_FILE to
DITTO_CONFIG_FILE. GitLab Runner sets CONFIG_FILE to its own TOML
config in job env, which broke vite's loader.
App-side changes:
- ios/App/App.xcodeproj/project.pbxproj: team GZLTTH5DLM stamped in;
MARKETING_VERSION gets stamped from the tag at build time.
- public/CHANGELOG.md, package.json: v2.14.3.
Skills + AGENTS.md updated to reflect the six-job pipeline (test /
deploy unchanged, build now has two jobs, release / publish updated)
and to document Mac-runner operations, fastlane match cert rotation,
and local debugging workflows.
The combined skill conflated two unrelated jobs: (1) design-time
decisions when authoring a new kind (NIP-vs-custom, ranges, tag design,
NIP.md) and (2) implementation-time checklist for wiring rendering into
Ditto's many UI touchpoints. The single description sentence was
unwieldy, and its trigger ('introducing a new kind... or registering a
kind in the UI') was phrased around the author's perspective — it
didn't match user phrasing like 'support displaying kind X' or 'render
NIP-Y', so I skipped loading it when implementing NIP-84 and missed
half the registration points.
Split into:
- nostr-kind-design — NIP-vs-custom decision, kind ranges, tag design,
content-vs-tags, NIP.md. Loads when minting or extending a schema.
- nostr-kind-rendering — the multi-location UI registration checklist.
Loads when rendering a kind Ditto doesn't yet display, or when asked
to 'support / display / render' a NIP or kind number.
Expanded the rendering checklist with the points I missed during the
NIP-84 pass: the six-file notification stack, the four-file AppConfig
triple for feed-toggle keys, sidebar icon registration, AppRouter route
wiring, shouldHideFeedEvent spam guards, and a 'bugs that signal a
missed step' section so the checklist reads as diagnostic too. Also
flagged the embedded-previews trap where skipping the dispatcher branch
silently feeds quoted prose through the kind-1 tokenizer.
Updated both AGENTS.md references to point at the two new skills.
The OpenCode system prompt's bash-tool instructions include 'Only create
commits when requested by the user' and 'NEVER commit changes unless
the user explicitly asks you to.' Those rules were overriding the
existing AGENTS.md directive and causing the agent to stop at
'validation passed' without committing, repeatedly, across sessions.
Spell out the conflict by name (linking upstream PR #25198), state
explicitly that AGENTS.md takes precedence, and enumerate the failure
modes (asking permission, waiting to be asked, treating uncommitted
changes as 'done') so the agent has something concrete to match against
when it catches itself hesitating.
Deletes the DM implementation (DMProvider, DMContext, useDMContext,
useConversationMessages, DMChatArea, DMConversationList,
DMMessagingInterface, DMStatusInfo, dmMessageStore, dmUtils,
dmConstants, the orphaned pages/Messages.tsx, and the
nostr-direct-messages skill) and removes the corresponding wrapper
from the provider tree in App.tsx.
The feature was already disabled (dmConfig.enabled = false), so this
removes no user-visible functionality -- only ~1,600 lines in
DMProvider and the associated UI/context/hooks. The nip44/nip04 signer
paths used by drafts, letters, mute lists, and encrypted settings are
unrelated and remain. Kind 1222 voice messages are a public-feed
feature and stay.
Documentation cleanup: strip the three DM mentions from AGENTS.md
(Project Structure, App.tsx provider list, Specialized Workflows skill
pointer) and the Private Messaging bullet from README.md's feature
list. Historical CHANGELOG entries are preserved.
Extract eleven topic areas into loadable skills so AGENTS.md can serve
as a scannable overview instead of a specification dump. The file
shrinks from 1480 to 358 lines (~76%) while keeping every concrete
rule, critical code pattern, and pointer that an agent needs on first
read.
New Ditto-specific skills:
- nostr-kinds: NIP-vs-custom-kind decision framework, kind ranges,
tag design, content-vs-tags, NIP.md update rule, and Ditto's
seven-location UI registration checklist for new kinds (NoteCard,
PostDetailPage, extraKinds.ts, KIND_LABELS/KIND_ICONS in
CommentContext, WELL_KNOWN_KIND_LABELS in ExternalContentHeader,
EmbeddedNote/EmbeddedNaddr, ReplyComposeModal).
- nostr-publishing: useNostrPublish, the read-modify-write pattern
via fetchFreshEvent + prev for replaceable/addressable events,
published_at contract, and d-tag collision prevention.
- nostr-queries: the standard useNostr + useQuery pattern,
combining kinds into one filter to avoid rate limits, and the
NIP-52 validator walkthrough.
- theming: @fontsource install flow, the Ditto runtime font-loader
path (sanitizeUrl + sanitizeCssString), color scheme variables,
useTheme toggle, and the isolate + negative-z-index gotcha.
- ci-cd-publishing: Zapstore NIP-46 bunker auth (zsp +
nip46-auth.mjs), nsite deploys (nsyte nbunksec + configured
relays/servers), and Google Play AAB uploads via fastlane supply
(service-account JSON base64 encoding and rotation).
- capacitor-compat: WKWebView/WebView limitations, the
downloadTextFile / openUrl helpers in src/lib/downloadFile.ts,
platform detection, and the full plugin list.
- git-workflow: pre-commit validation order and the Regression-of:
trailer convention used by the release skill's changelog
generator.
Ported from mkstack, lightly adapted where needed:
- nip19-routing: root-level /:nip19 routing and filter construction
patterns (adapted to reference Ditto's existing NIP19Page).
- nostr-relay-pools: nostr.relay() and nostr.group() for targeted
queries.
- nostr-encryption: NIP-44 / NIP-04 via the user's signer.
- file-uploads: useUploadFile + Blossom + NIP-94 imeta tag
construction.
AGENTS.md itself now follows mkstack's density — concrete rules inline,
one code example per section, pointer to the matching skill for details.
The enumerations that previously bloated it (every shadcn primitive,
every hook, every Capacitor plugin, the full NostrMetadata type dump,
the NIP-19 prefix reference table, etc.) are either removed in favor
of "ls the directory" or moved into their skill.
Adds three new skills extracted from mkstack's restructured AGENTS.md
and trims the corresponding AGENTS.md sections to match.
- nostr-security: XSS threat model, URL and CSS sanitization patterns,
author filtering for trust-sensitive queries, NIP-72 moderation
walkthrough, and a pre-merge checklist. The skill's references to
sanitizeUrl and sanitizeCssString are pointed at Ditto's existing
helpers in src/lib/sanitizeUrl.ts and src/lib/fontLoader.ts.
- testing: Vitest + TestApp conventions, mocked browser APIs, and the
project policy on when (not) to create new test files.
- nip85-stats: reference documentation for NIP-85 Trusted Assertion
stats (kinds 30382, 30383, 30384) including a ready-to-copy
useNip85Stats hook for future use; not currently wired into Ditto.
AGENTS.md changes:
- Shrink the Nostr Security Model section from a verbose kinds-and-URLs
walkthrough into a compact rule list plus a spoof-vs-authors example,
with a pointer to the new skill.
- Trim the Writing Tests section to the policy + skill pointer, moving
the TestApp example and browser-API mocks into the skill.
- Demote Loading States / Empty States from a top-level section to a
subsection under CRITICAL Design Standards so the document's
top-level headings describe domains, not presentation details.
Net: AGENTS.md 1654 -> 1480 lines (~10%).
When a commit fixes a bug introduced by an identifiable prior commit,
the fix should record the offending short SHA in a Regression-of:
trailer at the bottom of the commit message body.
This is a standard Git trailer (parseable by git interpret-trailers)
that makes intra-release regression detection trivial: the release
skill can now read the trailer directly instead of hunting through
git log and git blame to figure out whether a 'Fixed' entry actually
describes a bug a shipped user ever saw.
- AGENTS.md: new 'Attributing Regressions' subsection under Using Git
with the convention, when-to-add/skip rules, and tracing tips.
- .agents/skills/release/SKILL.md: Step 5.2 now has a fast path that
reads Regression-of trailers via 'git log --format=%(trailers:...)',
with the existing manual git log/blame approach as fallback.
- CONTRIBUTING.md: brief mention in the Bug fixes section and a new
self-review checklist item pointing at AGENTS.md.
- Sanitize event-sourced URLs before CSS url() interpolation in
ProfileCard banner and letter stationery background (closes H-1, H-2)
- Sanitize event-sourced font families at the parse layer and in letter
card/detail consumers that bypass resolveStationery (closes M-6)
- Export sanitizeCssString for broader reuse
- Route NWC wallet connection URIs and active pointer through a new
useSecureLocalStorage hook, storing in iOS Keychain / Android KeyStore
on native (closes M-1)
- Add removeItem to secureStorage
- Add Android backup/data-extraction rules that exclude WebView storage
and Capacitor secure-storage SharedPreferences so wallet credentials
don't leak via Google Auto Backup (closes M-5)
- Document that GOOGLE_PLAY_SERVICE_ACCOUNT_JSON must be base64-encoded
to match what the CI job expects (closes M-2)
Nostr events are untrusted user input. Any URL extracted from event tags
or metadata must be validated before use in any context — not just
navigable hrefs, but also img src, CSS url(), and style attributes.
Changes:
- Theme events (kind 16767/36767): validate background and font URLs
through sanitizeUrl() at parse time in themeEvent.ts
- Badge definitions (kind 30009): validate image and thumb URLs through
sanitizeUrl() at parse time in parseBadgeDefinition.ts
- Font family names: sanitize with an allowlist regex before
interpolation into CSS declarations in fontLoader.ts
- Profile fields: replace weak startsWith('http://') checks with
sanitizeUrl() in ProfileRightSidebar and ProfilePage
- Community descriptions: validate extracted URLs through sanitizeUrl()
in CommunityContent.tsx
- AGENTS.md: mandate unconditional URL sanitization for all
event-sourced URLs regardless of rendering context, document CSS
injection prevention guidelines
Add a shared sanitizeUrl() utility that validates URLs are well-formed
https: before they reach href attributes, window.open(), or openUrl().
Apply sanitization across all components that render untrusted URLs:
- CalendarEventDetailPage: r-tag links
- ZapstoreAppContent: url and repository tags
- ZapstoreReleaseContent: asset url tags passed to openUrl()
- AppHandlerContent: web handler tags and metadata.website
- NsiteCard: source tag
- GitRepoCard: web tag URLs passed to openUrl()
- FileMetadataContent: url tag used in download href
- ProfilePage: metadata.website (tighten weak startsWith check)
- useUserStatus: r-tag URL
Document sanitizeUrl usage in AGENTS.md for future agent use.
Extend useNostrPublish with an optional `prev` property on the event
template. For replaceable and addressable kinds, the hook automatically
manages published_at:
- First publish (no prev): set published_at equal to created_at
- Update (prev provided): preserve published_at from the old event
- Old event lacks published_at: don't fabricate one
- Caller already set published_at in tags: leave it alone
Callers pass `prev` when they have the old event from fetchFreshEvent,
giving the hook everything it needs without extra network requests.
Updated all 11 call sites that publish replaceable or addressable events.
Documents the prev convention in AGENTS.md.
Use nsyte CLI with NIP-46 nbunksec bunker credential to deploy
the web app to nsite on every default branch push. Downloads the
nsyte binary, builds the Vite app, and uploads to configured
Blossom servers and Nostr relays with SPA fallback routing.
Rename the checklist item from 'Inline embeds / quote posts' to
'Embedded note cards' with explicit file paths, explain that kinds
with tag-based media may need attachment indicator updates, and add
a note distinguishing EmbeddedNote components from the NoteCard
compact prop to prevent confusion.
The <a download> and <a target="_blank"> patterns don't work in
WKWebView. Add downloadTextFile() and openUrl() utilities in
src/lib/downloadFile.ts that use @capacitor/filesystem and
@capacitor/share on native platforms, falling back to standard
browser behavior on web.
Update all call sites: onboarding key download (InitialSyncGate,
SignupDialog), image lightbox buttons (ImageGallery, ProfilePage).
Document Capacitor compatibility constraints in AGENTS.md.
Quote posts and hover previews should always be compact (author + title/content).
Rendering a full NoteCard with action headers, buttons, and nested card components
inside a quote post or hover card is too much UI for an inline context.
- Remove NOTECARD_KINDS from EmbeddedNote.tsx and EmbeddedNaddr.tsx
- Remove NoteCard imports from both embed components
- Add tag-based metadata fallback (title/name/d, summary/description) to
EmbeddedNoteCard for non-text kinds with empty content
- Update AGENTS.md checklist to reflect that embeds no longer need per-kind registration