Add Boundaries panel to the UI: precise personal avoid-terms first

- New BoundariesPanel.svelte: gentle, device-local controls. Avoid words/phrases
  first (the trust-critical piece), then 'Paused for now' and 'Always hidden',
  each with easy remove. Reassures 'nothing leaves this device'; adding a term
  refreshes the brief/feed immediately.
- Quiet 'Boundaries' toggle (active indicator) replaces the old calm bar, keeping
  the first viewport calm.
- Wording stays gentle throughout: avoid / pause / hide / boundaries — never
  blocked/banned/blacklist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-05-30 22:50:11 +00:00
parent 15d51fb8fd
commit b9ecebffde
2 changed files with 153 additions and 17 deletions
+20 -17
View File
@@ -5,6 +5,7 @@
import MoodNav from '$lib/components/MoodNav.svelte';
import Lane from '$lib/components/Lane.svelte';
import ArticleCard from '$lib/components/ArticleCard.svelte';
import BoundariesPanel from '$lib/components/BoundariesPanel.svelte';
let moods = $state([]);
let selected = $state('today');
@@ -12,6 +13,7 @@
let feed = $state([]);
let lanes = $state([]);
let userPrefs = $state(P.blank());
let showBoundaries = $state(false);
let loading = $state(true);
let error = $state('');
@@ -52,16 +54,14 @@
if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
}
function applyAction(kind, value) {
P[kind]?.(userPrefs, value);
function refreshPrefs() {
userPrefs = { ...userPrefs };
P.save(userPrefs);
select(selected);
}
function resetFilters() {
userPrefs = P.blank();
P.save(userPrefs);
select(selected);
function applyAction(kind, value) {
P[kind]?.(userPrefs, value);
refreshPrefs();
}
onMount(async () => {
@@ -80,11 +80,14 @@
<MoodNav {moods} {selected} onselect={select} />
{/if}
{#if filtersOn}
<div class="calmbar">
<span>Calm filters on — your feed is personalized on this device.</span>
<button onclick={resetFilters}>reset</button>
</div>
<div class="toptools">
<button class="boundaries" class:on={filtersOn} onclick={() => (showBoundaries = !showBoundaries)}>
{filtersOn ? 'Boundaries ·' : 'Boundaries'}
</button>
</div>
{#if showBoundaries}
<BoundariesPanel prefs={userPrefs} onchange={refreshPrefs} onclose={() => (showBoundaries = false)} />
{/if}
{#if loading}
@@ -131,13 +134,13 @@
{/if}
<style>
.calmbar {
display: flex; align-items: center; justify-content: center; gap: 12px;
background: var(--sage-soft); color: var(--sage-deep);
border-radius: 999px; padding: 6px 16px; margin: 6px auto 0; width: fit-content;
font-size: 0.85rem;
.toptools { display: flex; justify-content: flex-end; margin: 2px 0 0; }
.boundaries {
background: none; border: none; color: var(--muted);
font-size: 0.82rem; padding: 4px 2px; letter-spacing: 0.01em;
}
.calmbar button { background: none; border: none; color: var(--sage-deep); text-decoration: underline; font-size: 0.85rem; }
.boundaries:hover { color: var(--sage-deep); }
.boundaries.on { color: var(--sage-deep); font-weight: 600; }
.kicker {
font-size: 0.82rem; text-transform: uppercase; letter-spacing: 0.12em;
color: var(--gold); margin: 22px 0 14px; font-family: var(--sans); font-weight: 700;