Release v0.0.9 - Add reckless mode with whitelisted apps

- Add reckless mode checkbox to auto-approve signing requests
- Implement whitelisted apps management page
- Reckless mode logic: allow all if whitelist empty, otherwise only whitelisted hosts
- Add shouldRecklessModeApprove() in background service worker
- Update default avatar to Plebeian Market Account icon
- Fix manifest version scripts to strip v prefix for browsers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 10:56:45 +01:00
parent 5550d41293
commit ddb74c61b2
27 changed files with 952 additions and 116 deletions

View File

@@ -11,6 +11,30 @@
</button>
</div>
<div class="reckless-mode-row">
<label class="reckless-label" (click)="onToggleRecklessMode()">
<input
type="checkbox"
[checked]="isRecklessMode"
(click)="$event.stopPropagation()"
(change)="onToggleRecklessMode()"
/>
<span
class="reckless-text"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Auto-approve all actions. If whitelist has entries, only those apps are auto-approved."
>Reckless mode</span>
</label>
<button
class="gear-btn"
title="Manage whitelisted apps"
(click)="onClickWhitelistedApps()"
>
<i class="bi bi-gear"></i>
</button>
</div>
@let sessionData = storage.getBrowserSessionHandler().browserSessionData;
@let identities = sessionData?.identities ?? [];
@@ -34,7 +58,7 @@
class="avatar"
[src]="getAvatarUrl(identity)"
alt=""
(error)="$any($event.target).src = 'assets/person-fill.svg'"
(error)="$any($event.target).src = 'person-fill.svg'"
/>
<span class="name">{{ getDisplayName(identity) }}</span>
<lib-icon-button

View File

@@ -28,13 +28,66 @@
grid-column-end: 2;
grid-row-start: 1;
grid-row-end: 2;
font-size: 20px;
font-weight: 500;
font-family: var(--font-heading);
font-size: 24px;
font-weight: 700;
letter-spacing: 0.1rem;
justify-self: center;
height: 32px;
}
}
.reckless-mode-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
margin-bottom: 12px;
background: var(--background-light);
border-radius: 8px;
.reckless-label {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
.reckless-text {
font-size: 14px;
color: var(--foreground);
}
}
.gear-btn {
background: transparent;
border: none;
color: var(--muted-foreground);
padding: 4px 8px;
cursor: pointer;
border-radius: 4px;
transition: color 0.15s ease, background-color 0.15s ease;
&:hover {
color: var(--foreground);
background: var(--background-light-hover);
}
i {
font-size: 16px;
}
}
}
.empty-state {
height: 100%;
display: flex;

View File

@@ -24,6 +24,10 @@ export class IdentitiesComponent implements OnInit {
// Cache of pubkey -> profile for quick lookup
#profileCache = new Map<string, ProfileMetadata | null>();
get isRecklessMode(): boolean {
return this.storage.getSignerMetaHandler().signerMetaData?.recklessMode ?? false;
}
async ngOnInit() {
await this.#profileMetadata.initialize();
this.#loadProfiles();
@@ -40,7 +44,7 @@ export class IdentitiesComponent implements OnInit {
getAvatarUrl(identity: Identity_DECRYPTED): string {
const profile = this.#profileCache.get(identity.id);
return profile?.picture || 'assets/person-fill.svg';
return profile?.picture || 'person-fill.svg';
}
getDisplayName(identity: Identity_DECRYPTED): string {
@@ -60,4 +64,13 @@ export class IdentitiesComponent implements OnInit {
async onClickSelectIdentity(identityId: string) {
await this.storage.switchIdentity(identityId);
}
async onToggleRecklessMode() {
const newValue = !this.isRecklessMode;
await this.storage.getSignerMetaHandler().setRecklessMode(newValue);
}
onClickWhitelistedApps() {
this.#router.navigateByUrl('/whitelisted-apps');
}
}