The repositories feed never special-cased TopFilter.Mine, so it inherited
the shared Mine→all-follows fallback — "Mine" behaved identically to "All
Follows", making both selectors look unresponsive when toggled.
Mirrors the music/badges/communities/nsites pattern:
- GitRepositoriesFeedFilter: when the list is Mine, match repositories
authored by the logged-in user (feed + applyFilter).
- GitRepositoriesSubAssembler: when the list is Mine, query the user's own
repositories by author against their outbox relays (new
filterGitRepositoriesMine), bypassing the follow-list machinery.
"All Follows" already routed through the standard follows filter; it now
visibly differs from "Mine".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Removes the old boxed "Overview" section block (About/Links/Topics/
Maintainers cards) that was stacked on top of the new dashboard, which made
the home read as two designs. Its useful content now lives in the dashboard
language:
- New RepoHero: owner avatar + "name / project" title (GitHub-style), with
description and topic chips.
- RepoMaintainersRow: a compact maintainer avatar cluster.
- Drops the Links card (clone/web URLs) as low-value.
Deletes GitRepositoryOverview.kt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Replaces the read-only zap/reaction/comment counts with the canonical
ZapReaction / LikeReaction / ReplyReaction actions, so the repository home
now supports one-tap zaps (with the amount dialog), reactions, and replying
to the repo announcement — each with its live counter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Turns the repository home into a data-rich dashboard combining git facts
with the Nostr social layer:
- Social row: live zap / reaction / comment counts on the repo announcement
(via observeNoteZaps/Reactions/ReplyCount).
- Stat tiles: branches, tags, file count, and last-updated relative time.
- Language breakdown bar: proportional colored segments computed from the
snapshot's file tree by extension (new GitRepoSnapshot.walkFileNames()).
- Last-commit strip: tip commit summary, author, time and short SHA, exposed
up-front via the new GitRepoSnapshot.tipCommit (no extra fetch).
- Nav cards now show open issue / PR counts.
- Recent-activity pulse: newest issues/patches merged from the feeds.
quartz: GitRepoSnapshot gains tipCommit (parsed in open()) and
walkFileNames() to enumerate blob paths for the language bar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Replaces the five-tab repository screen (Readme/Code/Overview/Issues/Patches)
with a scrollable Project Home and three dedicated screens:
- GitRepositoryScreen is now the home: repository facts (from the former
Overview), Code / Issues / Pull Requests navigation cards, and the README
rendered inline — all in one scroll.
- New GitRepositoryCode/Issues/Pulls screens, each owning its own
disappearing top bar, reached via new addressable routes.
This removes tab overflow and the swipe-paging between heavy screens, and
fixes the per-tab header issues structurally: the Code browser's branch/tag
and file-search header now live inside the scroll list, so they track the
disappearing top bar instead of staying pinned below it.
README and Overview were refactored into embeddable, non-scrolling sections
(GitReadmeSection, GitRepositoryOverviewSections) so the home can compose
them in a single scroll container.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
The KMP lifecycle-viewmodel artifact used by commons doesn't expose the
create(Class<T>) ViewModelProvider.Factory override (only the desktop/JVM
target hit this), so the factory now lives in amethyst alongside the
viewModel() call, mirroring the NestViewModel pattern. The commons
ViewModel keeps only platform-agnostic state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
The status feed drew its filter-chip header at the top of the content area
(under the disappearing top bar) while the feed's LazyColumn separately
re-applied the full bar-height inset as content padding, leaving an empty
band above the first item. The header now consumes the scaffold top inset
itself and the inner feed renders with the top inset zeroed, matching the
Code tab's padding pattern.
Also adds a "Mine" option to the git repositories top-nav filter so the
feed can show only the logged-in user's own repositories. The feed side
already resolves TopFilter.Mine generically; this just exposes it via a
dedicated gitRepositoryRoutes catalog (kind3 + Around Me + Global + Mine).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Relocates the two app-agnostic pieces of the NIP-34 code browser into commons
so the desktop front end can reuse them verbatim:
- GitRepositoryBrowserViewModel (+ GitBrowseState) → commons jvmAndroid
nip34Git package. Pure StateFlow ViewModel over quartz's GitHttpClient;
no Android/AccountViewModel/INav dependency.
- CodeHighlighter → commons commonMain nip34Git/ui. Pulls the Apache-2.0
dev.snipme:highlights dependency into commons commonMain.
Amethyst composables now import both from commons. The screen-level
composables stay app-side, matching the commons convention that shared UI
never takes AccountViewModel/INav.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Adds NIP-51 (kind 10018) repository bookmarking with a star toggle in the
git repository top bar, backed by a new GitRepositoryListState in commons.
Removal rebuilds the public tag set and re-signs so encrypted private
bookmarks are preserved without decryption.
Adds a label-filter chip row and open/closed item counts to the Issues and
Patches & PRs status feeds. The active feed's distinct labels drive the
chips; selecting one filters the rendered list, and a stale selection is
dropped when switching status.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Add a History action to the Code tab that shows a git-log-style list of the
branch's recent commits, and opens the diff a commit introduced (vs its first
parent) with the shared diff viewer.
quartz: GitCommit + a commit-object parser (skips multi-line headers like a
signed gpgsig), and GitHttpClient.loadHistory() — a shallow tree:0 fetch
(commits only) walked most-recent-first. The fetch filter is generalized from
a blob:none boolean to a filter spec so tree:0 can be requested. Unit-tested
against a real SSH-signed commit object.
amethyst: GitCommitLog (log list + per-commit diff), a History button on the
repo header, and ViewModel hooks (loadHistory / commitDiff).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Emphasize what changed inside a modified line, not just the whole line.
A new pure IntralineDiff helper pairs each delete with its corresponding
add within a hunk and trims the common prefix/suffix to the differing
middle; GitDiffView layers a stronger add/delete background over just that
character span, on top of the existing syntax highlighting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Pull requests reference a clone URL + commit rather than embedding a patch,
so their changes were invisible. Now the app computes and renders them.
- quartz: a pure Myers O(ND) line-diff (LineDiff) producing the same
GitDiffHunk model the embedded-patch parser uses, unit-tested against git
-U3 output and with a reconstruction property check.
- quartz: GitHttpClient.computeDiff(cloneUrl, head, base?) — fetches both
commit trees, finds changed files by oid, batch-fetches the differing
blobs and line-diffs each into a ParsedPatch (base = merge base, or HEAD).
- amethyst: a "View changes" section on the PR card that loads the diff over
the git client and renders it with the shared GitDiffView.
- amethyst: GitStatusActions generalizes the issue open/close controls to
issues, patches and PRs — patches/PRs additionally get "Mark merged"
(GitStatusAppliedEvent). Visible to the author and repo owner/maintainers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Add an owner-only Edit action to the git repository screen's top bar that
opens a settings dialog. Because the repository event (kind 30617) is
addressable, saving republishes it under the same d-tag — preserving the
earliest-unique-commit, relays, maintainers and personal-fork flag — while
letting the owner edit the name, description, clone/web URLs and topics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Code browser:
- Branch & tag switching: the client now exposes all refs from ls-refs;
a branch/tag picker on the repo header reloads the tree at the chosen
ref (GitRepositoryBrowserViewModel.switchRef).
- In-tree filename search: a search field filters the whole tree
(GitRepoSnapshot.searchFiles) and shows matching paths.
- Image preview: png/jpg/gif/webp/bmp blobs render via Coil instead of the
binary notice.
Issues:
- Labels at creation: the new-issue composer takes a comma/space separated
label list, written as NIP-34 `t` tags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
NSite manifests (kinds 15128/35128) were never found because all coordinate
construction hardcoded napplet kinds (15129/35129). The connected apps list
and detail screen therefore showed no icon or title for nsite entries.
- Add rememberManifestIconModel(author, identifier) in NappletFavoriteIcon.kt:
tries napplet coord first, falls back to nsite coord. resolveIconBlob already
handled all four event types — just needed the right coordinate.
- Replace rememberNappletManifest with rememberManifestEvent in
ConnectedAppsScreen.kt: watches both napplet and nsite notes, returns
whichever carries an event. NappletAppCard dispatches on the event type
to extract title/icon from NappletManifest, RootSiteEvent, or NamedSiteEvent.
- ConnectedAppDetailScreen.AppIdentityHeader: swap hardcoded kind+
rememberNappletIconModel call for rememberManifestIconModel(author, identifier).
- resolveNappletMeta in NappletManifestLookup.kt: widen the cache filter to
include nsite kinds and dispatch title/icon extraction across all four types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
NIP-34 pull-request update events (kind 1619) revise a PR with a newer
commit / merge base, but the repository screen neither subscribed to them
nor reflected them, so updated PRs looked stale.
- Subscribe to GitPullRequestUpdateEvent.KIND in RepositoryContentKinds so
updates reach the repo screen.
- Add GitPullRequestUpdateIndex (mirrors GitStatusIndex): tracks the latest
update per parent PR id from LocalCache, exposed as a StateFlow.
- Fold the latest update into the parent PR card: the current commit, merge
base and clone URLs now reflect the newest revision, with a "Revised"
marker on both the PR card and the compact Patches-tab row.
Updates are folded into their parent PR rather than listed as separate rows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
- Capability rows now open a dialog with radio options (Ask each time /
Always allow / Never allow) instead of the confusing Switch+revoke
button combo; requiresPerUseConsent capabilities omit "Always allow"
- AppIdentityHeader now reactively loads napplet/nsite icons via
rememberNappletIconModel using the full kind:pubkey:identifier coord
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
The toggle ON/OFF state was ambiguous (users couldn't tell if OFF meant
"ask me" or "permanently deny"). The revoke/Block icon looked like a deny
action but actually resets to "ask me each time".
- Add "Allow always" (primary) / "Never allow" (error) label above the
Switch so the toggle's two states are explicit
- Swap MaterialSymbols.Block for MaterialSymbols.Refresh on the reset
button — Refresh reads as "start over / go back to asking"
- Update its content description to "Ask me each time"
- Rename "Blocked" to "Requires per-use approval" for per-use-consent
capabilities to explain why there's no toggle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Build out the repository screen into a project-review hub.
PR/patch diff viewer:
- quartz: UnifiedDiffParser turns a NIP-34 patch (kind 1617) `git
format-patch` body into a commit message + structured per-file diffs,
bounding each hunk by its header counts so the mbox "-- " signature is
never miscounted. Unit-tested against a real git-generated patch.
- amethyst: GitDiffView renders a GitHub-style file-by-file diff —
stat summary, collapsible file cards, +/- line coloring, old/new line
numbers, and per-line syntax highlighting reused from the code browser
(size-guarded). Wired into the patch card; feeds keep the compact preview.
Issue management:
- New-issue composer (subject + body) that builds, signs and broadcasts a
GitIssueEvent via the account signer; reachable from the Issues tab.
- Author/maintainer Open/Close controls on the issue card, publishing
GitStatusOpen/Closed events that flow back through GitStatusIndex.
Issues tab polish:
- Open/Closed filter chips, label (#topic) chips on each row.
Adds git_diff_files_changed plural and issue/diff strings. No new icons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
The raw event JSON toggle in the signer consent dialog is now labeled
"Show Event" / "Hide Event" instead of the generic "See more" / "See less".
The expanded JSON block also gains horizontal scroll (softWrap=false +
horizontalScroll) so wide event JSON doesn't get clipped on narrow screens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Web app entries (browser:https://...) now display:
- The captured favicon from BrowserIconRegistry (same source as the
bottom-nav favourite website icon) in ConnectedAppsScreen,
ConnectedAppDetailScreen, and all three permission/consent dialogs
- The domain name (host) as the title instead of the full URL, via
OmniboxInput.hostOf() in loadDetailState, NappletConsentSummary,
buildSignerConsentInfo, and buildConnectInfo
- A globe icon (FavoriteApp.WebApp) instead of the grid icon
(FavoriteApp.NostrApp) in all consent dialog headers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Make the repository browser flagship-grade rather than utilitarian:
- Shared status components: spinner-backed loading box and an icon-led
message box with a styled retry action, replacing plain centered text.
- Code tab: a repo info bar (branch + short-commit chips, item count),
a scrollable chip breadcrumb, and polished entry rows with tinted icon
tiles, monospace filenames, folder/file color distinction, a trailing
chevron on folders, and hairline dividers.
- File viewer: a line-number gutter with horizontally scrollable,
syntax-highlighted code, a language/path bar with copy-to-clipboard,
and themed surfaces; richer binary/error states.
- README tab: spinner loading + icon-led empty/error states.
Adds a git_repo_item_count plural and copy/text strings. No new icons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
The permission ledger stores browser-visited origins as `browser:<url>`
where "browser" is a sentinel non-pubkey. All previous code treated every
entry as a napplet, causing NPub.create("browser") to fail and
LocalCache.checkGetOrCreateAddressableNote to return null for every entry
-- so icons and titles never resolved.
Now ConnectedAppCard splits on author == "browser": web-app entries get a
globe icon (FavoriteApp.WebApp), the domain extracted from the URL as
title, and no npub row. Napplet entries (real hex pubkeys) continue using
the reactive manifest lookup. The relay subscription now also filters out
"browser" from the authors set so no invalid pubkey is sent to relays.
ConnectedAppDetailScreen receives the same fix in AppIdentityHeader, using
FavoriteApp.WebApp for browser entries so the globe icon appears there too.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Render the repository README in the first tab and add a Code tab that
browses the repo's file tree and renders source files (syntax-highlighted),
reading directly from the NIP-34 clone URL over the git smart-HTTP v2
protocol (works with GRASP/ngit bare servers as well as GitHub/GitLab).
quartz (jvmAndroid): a from-scratch git smart-HTTP v2 client — pkt-line
codec, packfile parser with OFS/REF delta resolution and SHA-1 oids,
tree/commit parsers, and a high-level browser that fetches a shallow
filter=blob:none snapshot (one request for the whole tree) and lazily
pulls file blobs on demand. Offline tests run against real captured
GitHub wire bytes plus a git-generated OFS-delta pack.
amethyst: README tab (rich markdown), Code tab (folders-first browser
with breadcrumb navigation + a file viewer that renders markdown or
syntax-highlighted source), a browser ViewModel, new UI strings, and a
Folder material symbol (font subset regenerated). Syntax highlighting
uses dev.snipme:highlights (Apache-2.0, permissive).
Tabs are now: README, Code, Overview, Issues, Patches & PRs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpNmN8CvP6HnEsdTGAjVUr
Replaces the one-shot fetchAll with a ComposeSubscriptionManager that
holds an open relay subscription to NIP-5D manifests (kinds 15129/35129)
for exactly the set of authors stored in the permission ledger, while
ConnectedAppsScreen is in composition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
The previous approach baked title/iconUrl into ConnectedAppEntry at
load time (a snapshot) and passed pubkey:identifier to
rememberNappletIconModel which expects kind:pubkey:dtag — so both
title and icon never updated from LocalCache.
Now:
- ConnectedAppEntry only holds coordinate + signerPolicy
- Each ConnectedAppCard builds the full kind:pubkey:dtag coordinate
(kind 15129 for root napplets with empty identifier, 35129 for named)
- rememberNappletManifest observes the live AddressableNote in
LocalCache so title/iconUrl update reactively as manifests arrive
- rememberNappletIconModel receives the correct full coordinate so
blossom blob icons load exactly like FavoriteAppsScreen
- The relay fetch now covers all connected apps (not just those with
missing iconUrl) so manifests arrive promptly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- Extract kindDisplayName() to KindDisplayName.kt and add kindNameFor()
helper that picks the translated string resource when available,
falls back to KindNames English map, then "k<number>"
- NostrSignerOpLabels: use kindNameFor() so "sign for Notes (kind: 1)"
is translated instead of always English
- ConnectedAppsScreen: wire rememberNappletIconModel so the card icons
load from blossom just like FavoriteAppsScreen does
- Remove the now-duplicate kindDisplayName() definition from
RelayInformationScreen.kt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- NostrSignerOp.SignKind label now renders "sign for <Kind Name> (kind: N)"
using KindNames.nameFor(), falling back to "sign kind N event" for unknowns
- Fix ConnectedAppsScreen race: merge two LaunchedEffects into one so
items is set before observeEvents fires and before the relay fetch runs;
previously both could find items==null and return early, leaving icons blank
LocalCache.observeEvents<Event>(filter) handles both the initial snapshot and
subsequent insertions via the observables registry, replacing the manual
bundle-scan over newEventBundles. Fewer moving parts and the filter is scoped
to manifest kinds rather than scanning every arriving event bundle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- Add authorPubKey (npub) to ConnectedAppEntry and show it in each card below the
domain, so users can verify who published the napplet/nsite
- Increase icon to 48dp and use surfaceVariant card background to match the detail
screen's AppIdentityHeader style
- After the initial cache-based load, subscribe to LocalCache.live.newEventBundles
and re-resolve metadata (title + icon) for all entries whenever a manifest event
(kind 15129 / 35129) arrives — handles manifests delivered by any relay subscription
- For apps whose icon is not yet cached, issue a one-shot relay fetch (kinds 15129/35129
for the relevant author set) via account.client.fetchAll; inject results into
LocalCache.justConsume so the newEventBundles observer picks them up and updates
the list without a full reload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
allPolicies() keyed the LargeCache on the filename ("nsp_HASH") while storeFor()
keyed it on the coordinate string. Both point at the same .preferences_pb file,
so getOrCreate created two live DataStore instances for one file —
DataStore's own singleton guard then threw IllegalStateException.
Fix: use file.absolutePath as the cache key in both code paths so the second
call always returns the already-open DataStore instance instead of creating a
new one.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- Make all four consent actions (Always Allow / Allow Once / Deny Once / Always Deny)
full-width OutlinedButton/Button so they are visually consistent; remove TextButton
with left-aligned text that clashed with the centered primary buttons
- Move domain/coordinate label into the header Column directly below the subtitle so
the URL is contextually grouped with the app identity rather than floating near buttons
- Fix DataStore multiple-instances crash: add nappletPermissionStore and signerPermissionStore
as lazy singletons in AppModules; warm them up on IO thread to avoid StrictMode
DiskReadViolation; update NappletBrokerService and connected-apps screens to use the
shared singletons instead of creating independent instances
- Propagate iconUrl through NappletConsentInfo / NappletSignerConsentInfo / NappletConnectInfo
data classes; resolve napplet metadata (title + icon) in the broker-side builders
(NappletConsentSummary, NostrSignerOpLabels) so dialogs receive it ready to display
- Replace manual buildEventJson (org.json) with JacksonMapper.toJsonPretty(EventTemplate)
in NostrSignerOpLabels; add toJsonPretty(EventTemplate<*>) overload to JacksonMapper
- Delete dead NappletPermissionsScreen.kt and NappletSignerPermissionsScreen.kt (never
navigated to); remove Route.NappletPermissions and its composable registration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Replace the AlertDialog with the same Surface(extraLarge)/Dialog pattern
used by the signer consent and connect dialogs. Shows app icon + name +
capability category in the centered header, operation detail (with any
content preview) in a selectable surfaceVariant box, and the same button
hierarchy: Always allow (Button, primary) / Allow once (FilledTonalButton)
then Never allow / Not now as left-aligned TextButtons below a divider.
When the capability is per-use only (payments), Allow once is promoted
to the primary Button.
Replace the full-screen semi-transparent overlay with a floating card
Dialog (same Surface/shape/elevation as the signer consent dialog) so
both permission prompts look consistent. Header now shows the app icon,
name, and "wants to connect to your Nostr account" subtitle instead of
the generic "Connect to Nostr" headline. Block button style matches the
deny row in the signer consent.
Move AllowForOp to the primary Button (filled) since always allowing the
operation is the preferred choice, with AllowOnce as the secondary
FilledTonalButton. The time-bound and allow-all options stay in "More
options", keeping the nuclear allow-all less discoverable.
- Wrap content in a scrollable Box with Alignment.Center so the dialog
is vertically centered instead of stuck at the top
- Explicitly set onSurface color on the PolicyOption label so it stays
visible regardless of Surface background tint
- Fix buildConnectInfo to show the napplet identifier (e.g. "browser")
in the block button instead of the raw coordinate prefix (pubkey)
- Extract resolveNappletMeta() to NappletManifestLookup.kt, replacing
three private copies of the same manifest lookup across
ConnectedAppsScreen, ConnectedAppDetailScreen, NappletPermissionsScreen,
and NappletSignerConsentActivity.
- Extract PolicyCard composable to PolicyCard.kt, shared between
ConnectedAppDetailScreen and RelayAuthSettingsScreen (was duplicated).
- Extract NappletCapability.symbol() to NappletCapabilityExt.kt, shared
between ConnectedAppDetailScreen and NappletPermissionsScreen.
- Drop what-comments on kind 1/6/7 lines in NostrSignerPermissionLedger.
- Reword TrustedRelayListState stateIn comment to note private-tag absence
on first boot.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- Cancel on first-connect dialog now suppresses re-prompting for the
rest of the session (sessionCancelled set under signerConsentLock)
instead of showing the dialog on every subsequent request.
- PARANOID signer policy no longer silently bulk-grants ALLOW_ALWAYS
for all capabilities; the capability ledger is left empty so each
capability prompts individually, matching user intent.
- NostrSignerOp.Decrypt default changed from ALLOW → ASK in
reasonableDecision(); the branch is currently unreachable
(toSignerOp() never produces Decrypt) but ASK is the safer default
if a decrypt request type is added in future.
- TrustedRelayListState seeds its StateFlow from the synchronously
available cached relay set, eliminating a startup window where
IF_IN_MY_LIST incorrectly denied auth to relays in the user's list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Remove the signer self-gating bypass that allowed external (Amber/NIP-55)
and remote (NIP-46) signers to skip Amethyst's per-napplet consent UI.
All signer types now go through Amethyst's consent dialogs first; the
external signer then adds its own approval on top (double-prompting).
This lets users differentiate signing requests by app inside the external
signer, since Amethyst itself is the requesting app.
Also expand the REASONABLE policy to auto-approve Encrypt and Decrypt
operations, matching the intent that common/private-key operations that
apps routinely need are pre-approved at the "reasonable" trust level.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Replace the static placeholder glyph with the napplet manifest's
icon() URL loaded via FavoriteAppIcon (Coil AsyncImage + glyph
fallback). Both ConnectedAppsScreen and ConnectedAppDetailScreen now
resolve iconUrl alongside title from the event cache and display the
real app icon when available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
- Add AllowForSession and AllowUntil(expiresAt) signer grant types so users
can grant temporary access (session, 24h, 30d) from the consent dialog
- Track per-app lastUsed timestamp in NostrSignerPermissionStore and update
it on every granted signing operation
- Auto-expire timed grants: decide() clears expired op decisions before
returning, so no background sweep is needed
- Add NappletBroker.sessionAllows in-memory set for session grants (cleared
on broker destroy, never persisted)
- Implement full NIP-42 relay auth policy system:
- RelayAuthPolicy enum (ALWAYS / NEVER / IF_IN_MY_LIST) stored in
AccountSettings and persisted in LocalPreferences
- RelayAuthDecision (ALLOW / DENY) per-relay overrides in DataStore
- RelayAuthPermissionLedger combining global policy + per-relay overrides
- DataStoreRelayAuthPermissionStore writing to relay_auth.preferences_pb
- Wire relay auth into AuthCoordinator: subscribeLedger/unsubscribeLedger
lets each logged-in account contribute its own policy; signWithAllLoggedInUsers
now receives the relay URL so it can check the ledger before signing
- Update RelayAuthenticator (quartz) to pass relay URL in the signing lambda
- Add RelayAuthSubscription composable that subscribes both the account and
its ledger when a screen is active
- Add RelayAuthSettingsScreen: global policy radio picker + per-relay
override list with toggle and remove; reachable from Settings
- Add Route.RelayAuthSettings, AppNavigation wiring, and SettingsCatalog entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
Adds a unified "Connected Apps" entry to Account Settings that surfaces
all web apps that have connected to the user's Nostr key (both capability
grants and signer trust levels) in one place.
- Route.ConnectedApps (list) + Route.ConnectedAppDetail(coordinate) (detail)
- ConnectedAppsScreen: merges NappletPermissionLedger + NostrSignerPermissionLedger,
shows one card per app sorted alphabetically with trust-level chip
- ConnectedAppDetailScreen: identity header, editable trust-level picker
(Full Trust / Reasonable / Paranoid), op override rows with revoke, capability
switches, and a "Forget this app" button that clears all permissions
- Settings catalog entry under Account (Apps symbol, keyword-searchable)
- NappletsTopBar Tune icon now navigates to ConnectedApps instead of
the old capability-only NappletPermissions screen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT