Polish pass 6 + fix V4V auto-streaming not stopping on manual toggle

Polish:
- ArticleEditor: sentence case on all buttons (← Drafts, Write, Preview,
  Zen, Meta, Publish, ← Back, New draft)
- EditProfileForm: Save profile / Saving… / Saved ✓
- ImageField: Upload / Uploading…
- RelaysView: Add, Publish list, Remove dead, Discover relays (sentence case)
- InlineReplyBox: attachment remove x → ×, aria-labels on remove/attach/emoji

Bug fix:
- V4VIndicator: add userDisabledRef so manually turning off streaming
  prevents auto-start from re-engaging on subsequent play/pause/seek
  events for the same episode; resets automatically on new episode
- V4VIndicator: remaining amber-* colors → zap theme token
This commit is contained in:
Jure
2026-04-09 18:36:28 +02:00
parent bac52b15ac
commit e30e42971e
6 changed files with 34 additions and 19 deletions
+9 -9
View File
@@ -284,7 +284,7 @@ export function ArticleEditor() {
<header className="border-b border-border px-4 py-2.5 flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<button onClick={() => setActiveDraft(null)} className="text-text-dim hover:text-text text-[11px] transition-colors">
drafts
Drafts
</button>
<span className="text-text-dim text-[10px]">{wordCount > 0 ? `${wordCount} words` : "New article"}</span>
{activeDraft && !published && lastSaved && (
@@ -310,13 +310,13 @@ export function ArticleEditor() {
onClick={() => setMode("write")}
className={`px-3 py-1 transition-colors ${mode === "write" ? "bg-accent/10 text-accent" : "text-text-muted hover:text-text"}`}
>
write
Write
</button>
<button
onClick={() => setMode("preview")}
className={`px-3 py-1 transition-colors ${mode === "preview" ? "bg-accent/10 text-accent" : "text-text-muted hover:text-text"}`}
>
preview
Preview
</button>
</div>
@@ -325,14 +325,14 @@ export function ArticleEditor() {
className="px-3 py-1 text-[11px] border border-border text-text-muted hover:text-text transition-colors"
title="Focus mode (F11)"
>
zen
Zen
</button>
<button
onClick={() => setShowMeta((v) => !v)}
className={`px-3 py-1 text-[11px] border border-border transition-colors ${showMeta ? "text-accent border-accent/40" : "text-text-muted hover:text-text"}`}
>
meta
Meta
</button>
<button
@@ -340,7 +340,7 @@ export function ArticleEditor() {
disabled={!canPublish || publishing || published}
className="px-4 py-1 text-[11px] bg-accent hover:bg-accent-hover text-accent-text transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
>
{published ? "published ✓" : publishing ? "publishing…" : "publish"}
{published ? "Published ✓" : publishing ? "Publishing…" : "Publish"}
</button>
</div>
</header>
@@ -497,7 +497,7 @@ function DraftListView({ onNewDraft }: { onNewDraft: () => void }) {
<header className="border-b border-border px-4 py-2.5 flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<button onClick={goBack} className="text-text-dim hover:text-text text-[11px] transition-colors">
back
Back
</button>
<h2 className="text-text text-[13px] font-medium">Drafts</h2>
<span className="text-text-dim text-[11px]">{drafts.length} {drafts.length === 1 ? "draft" : "drafts"}</span>
@@ -506,7 +506,7 @@ function DraftListView({ onNewDraft }: { onNewDraft: () => void }) {
onClick={onNewDraft}
className="px-3 py-1 text-[11px] bg-accent hover:bg-accent-hover text-accent-text transition-colors"
>
new draft
New draft
</button>
</header>
@@ -515,7 +515,7 @@ function DraftListView({ onNewDraft }: { onNewDraft: () => void }) {
<div className="px-4 py-12 text-center space-y-2">
<p className="text-text-dim text-[13px]">No drafts yet.</p>
<p className="text-text-dim text-[11px] opacity-60">
Click "new draft" to start writing an article.
Click "New draft" to start writing an article.
</p>
</div>
)}
+4 -1
View File
@@ -184,8 +184,9 @@ export function InlineReplyBox({ event, name, rootEvent }: InlineReplyBoxProps)
onClick={() => removeAttachment(i)}
className="absolute -top-1.5 -right-1.5 w-4 h-4 bg-danger text-accent-text text-[10px] rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
title="Remove"
aria-label="Remove attachment"
>
x
×
</button>
</div>
))}
@@ -205,6 +206,7 @@ export function InlineReplyBox({ event, name, rootEvent }: InlineReplyBoxProps)
onClick={handleFilePicker}
disabled={uploading}
title="Attach image or video"
aria-label="Attach image or video"
className="text-text-dim hover:text-text text-[16px] transition-colors disabled:opacity-30"
>
+
@@ -213,6 +215,7 @@ export function InlineReplyBox({ event, name, rootEvent }: InlineReplyBoxProps)
<button
onClick={() => setShowReplyEmoji((v) => !v)}
title="Insert emoji"
aria-label="Insert emoji"
className="text-text-dim hover:text-text text-[16px] transition-colors"
>
+14 -2
View File
@@ -23,6 +23,10 @@ export function V4VIndicator() {
const playbackState = usePodcastStore((s) => s.playbackState);
const { setV4VEnabled, setV4VStreaming, addStreamedSats } = usePodcastStore.getState();
// Tracks when the user explicitly turned off streaming mid-episode.
// Prevents the auto-start effect from re-engaging until a new episode loads.
const userDisabledRef = useRef(false);
const autoEnabled = useV4VStore((s) => s.autoEnabled);
const defaultRate = useV4VStore((s) => s.defaultRate);
const capReachedReason = useV4VStore((s) => s.capReachedReason);
@@ -31,6 +35,11 @@ export function V4VIndicator() {
const hasWallet = !!nwcUri;
const hasRecipients = episode?.value && episode.value.length > 0;
// Reset user-disabled flag when a new episode loads
useEffect(() => {
userDisabledRef.current = false;
}, [episode?.guid]);
// Auto-start streaming when autoEnabled and V4V episode starts playing
useEffect(() => {
if (
@@ -39,6 +48,7 @@ export function V4VIndicator() {
hasRecipients &&
hasWallet &&
!v4vStreaming &&
!userDisabledRef.current &&
episode
) {
const intervalId = startStreaming(
@@ -77,10 +87,12 @@ export function V4VIndicator() {
if (!episode || !hasWallet) return;
if (v4vStreaming) {
userDisabledRef.current = true;
stopStreaming();
setV4VStreaming(false);
setV4VEnabled(false);
} else {
userDisabledRef.current = false;
const rate = autoEnabled ? defaultRate : v4vSatsPerMinute;
const intervalId = startStreaming(
episode,
@@ -116,9 +128,9 @@ export function V4VIndicator() {
capReachedReason
? "text-text-dim bg-border/50"
: v4vStreaming
? "text-amber-400 bg-amber-500/10 animate-pulse"
? "text-zap bg-zap/10 animate-pulse"
: hasRecipients
? "text-amber-400 bg-amber-500/10 hover:bg-amber-500/20"
? "text-zap bg-zap/10 hover:bg-zap/20"
: "text-text-dim hover:text-text"
}`}
title="Value 4 Value"
+1 -1
View File
@@ -89,7 +89,7 @@ export function EditProfileForm({ pubkey, onSaved }: { pubkey: string; onSaved:
disabled={saving || saved}
className="px-4 py-1.5 text-[11px] bg-accent hover:bg-accent-hover text-accent-text transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
>
{saved ? "saved ✓" : saving ? "saving…" : "save profile"}
{saved ? "Saved ✓" : saving ? "Saving…" : "Save profile"}
</button>
</div>
</div>
+1 -1
View File
@@ -40,7 +40,7 @@ export function ImageField({ label, value, onChange }: { label: string; value: s
className="px-2 py-1.5 text-[10px] border border-border text-text-dim hover:text-accent hover:border-accent/40 transition-colors disabled:opacity-40 disabled:cursor-not-allowed shrink-0"
title="Upload from your computer"
>
{uploading ? "uploading…" : "upload"}
{uploading ? "Uploading…" : "Upload"}
</button>
</div>
{uploadError && <p className="text-danger text-[10px] mt-1">{uploadError}</p>}
+5 -5
View File
@@ -277,7 +277,7 @@ export function RelaysView() {
onClick={handleAddRelay}
className="px-3 py-1.5 text-[11px] border border-border text-text-muted hover:text-accent hover:border-accent/40 transition-colors shrink-0"
>
add
Add
</button>
{loggedIn && !!getNDK().signer && (
<button
@@ -285,7 +285,7 @@ export function RelaysView() {
disabled={republishing}
className="px-3 py-1.5 text-[11px] border border-border text-text-muted hover:text-accent hover:border-accent/40 transition-colors disabled:opacity-40 shrink-0"
>
{republishing ? "publishing…" : "publish list"}
{republishing ? "Publishing…" : "Publish list"}
</button>
)}
</div>
@@ -303,7 +303,7 @@ export function RelaysView() {
disabled={removing}
className="px-3 py-1 text-[11px] border border-danger/30 text-danger hover:bg-danger/10 transition-colors disabled:opacity-40"
>
{removing ? "removing…" : "remove dead"}
{removing ? "Removing…" : "Remove dead"}
</button>
</div>
)}
@@ -384,9 +384,9 @@ function SuggestedRelays() {
{loading ? (
<span className="inline-flex items-center gap-1">
<span className="w-3 h-3 border border-accent border-t-transparent rounded-full animate-spin" />
discovering
Discovering
</span>
) : "discover relays"}
) : "Discover relays"}
</button>
</div>