Settings: fixed source set — toggle to hide, no add/remove (issue 4)
aa2ed843b70aa003470cb5fb98a2d608023793f6
humdrum-tiv <45084903+humdrum-tiv@users.noreply.github.com> · 2026-06-09 11:52
parent 1d3c8f7f
Settings: fixed source set — toggle to hide, no add/remove (issue 4) Source cards can no longer be added or deleted; every seeded source always exists and is just unchecked to hide it from the dashboard. Per-card Configure buttons already cover setup, so the confusing "Add [kind] [label]" row and the per-row ✕ delete button are gone. Added a one-line hint under the Sources list. Also mark issue 3 (calendar icalBuddy/EventKit path, shipped 13972f7) and issue 4 done in ISSUES.md. POST /api/sources and DELETE /api/sources/[id] routes remain but are now unused by the UI. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed
ISSUES.md +15 −13
@@ -29,22 +29,24 @@ covers both kinds; seeded in `scripts/seed.ts`.
- Friendlier "Add RSS feed" affordance still rides on the issue 4 Add-source UX rework; the
empty card currently shows the generic "Needs configuration → settings".
-## 3. Calendar still has issues
+## 3. Calendar still has issues ✅ done 2026-06-07
-Local Calendar.app reading is unreliable.
-- Large/subscribed calendars (e.g. sports fixtures) make the AppleScript `whose` query slow →
- hits the 60s timeout. `lib/sources/modules/calendar.ts`.
-- Investigate faster paths: `icalBuddy` if installed, or EventKit via a small helper, instead
- of Calendar.app AppleScript. Fall back to AppleScript only if needed.
-- Verify events actually render across calendars/time zones; confirm all-day handling.
-- Possibly cap per-calendar scan window or events count.
+Was: local Calendar.app `whose`-query slow → 60s timeout on big/subscribed calendars.
+- ✅ `calendar.ts` now prefers `icalBuddy` (reads EventKit directly — fast), found at
+ `/opt/homebrew/bin/icalBuddy` or `/usr/local/bin/icalBuddy` via `lib/mac.ts`.
+- ✅ Falls back to the AppleScript `whose` query only when icalBuddy isn't installed.
+- Shipped in commit `13972f7` "Calendar updates".
-## 4. Add-source field (bottom of Settings) doesn't make sense
+## 4. Add-source field (bottom of Settings) doesn't make sense ✅ done 2026-06-09
-The "Add [kind] [label]" row at the bottom of Settings → Sources is confusing.
-- Rethink the UX: clearer labels, grouping, or a different entry point (e.g. "+ Add a card"
- with a friendly picker that explains each source).
-- `components/SettingsView.tsx` → `AddSource`.
+Resolved by making the source set fixed instead of add/remove.
+- ✅ Removed the confusing "Add [kind] [label]" row (`AddSource`) entirely.
+- ✅ Removed the per-row ✕ delete button. Cards are no longer added or removed — every
+ seeded source always exists; you just **uncheck** to hide it from the dashboard. Per-card
+ Configure buttons handle setup.
+- Added a one-line hint under the Sources list explaining this.
+- `components/SettingsView.tsx`. (`addSource`/`removeSource` handlers dropped; the
+ `POST /api/sources` + `DELETE /api/sources/[id]` routes remain but are now unused by the UI.)
## 5. Refresh interval — minutes/hours, not just seconds
components/SettingsView.tsx +5 −70
@@ -7,7 +7,7 @@ import { downloadExport } from "@/lib/export";
import { uploadImport } from "@/lib/import";
import type { SourceState } from "@/lib/types";
import type { Settings } from "@/lib/schemas/setting";
-import { SOURCE_SIZES, SOURCE_KINDS } from "@/lib/schemas/source";
+import { SOURCE_SIZES } from "@/lib/schemas/source";
import { SIZE_LABELS } from "@/lib/layout";
const cardStyle = {
@@ -76,20 +76,6 @@ await Promise.all([
patchSource(a.source.id, { position: b.source.position }),
patchSource(b.source.id, { position: a.source.position }),
]);
- }
-
- async function addSource(kind: string, label: string) {
- await fetch("/api/sources", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ kind, label }),
- });
- await load();
- }
-
- async function removeSource(id: string) {
- await fetch(`/api/sources/${id}`, { method: "DELETE" });
- await load();
}
return (
@@ -213,11 +199,13 @@ state={it}
onPatch={(patch) => patchSource(it.source.id, patch)}
onUp={i > 0 ? () => move(i, -1) : undefined}
onDown={i < sources.length - 1 ? () => move(i, 1) : undefined}
- onDelete={() => removeSource(it.source.id)}
/>
))}
</div>
- <AddSource onAdd={addSource} />
+ <p className="mt-3 text-xs" style={{ color: "var(--text-faint)" }}>
+ Uncheck a card to hide it from the dashboard. Every source stays available — nothing
+ is ever removed.
+ </p>
</Section>
<Section title="Data">
@@ -255,13 +243,11 @@ state,
onPatch,
onUp,
onDown,
- onDelete,
}: {
state: SourceState;
onPatch: (patch: Record<string, unknown>) => void;
onUp?: () => void;
onDown?: () => void;
- onDelete?: () => void;
}) {
const { source } = state;
const [open, setOpen] = useState(false);
@@ -327,60 +313,9 @@ >
{open ? "Close" : "Configure"}
</button>
)}
- {onDelete && (
- <button
- className="text-xs px-1"
- style={{ color: "var(--text-faint)" }}
- aria-label={`Delete ${source.label}`}
- onClick={() => {
- if (confirm(`Remove "${source.label}" from the dashboard?`)) onDelete();
- }}
- >
- ✕
- </button>
- )}
</div>
{open && configurable && <SourceConfig state={state} onPatch={onPatch} />}
</div>
- );
-}
-
-function AddSource({ onAdd }: { onAdd: (kind: string, label: string) => void }) {
- const [kind, setKind] = useState<string>("news");
- const [label, setLabel] = useState("");
-
- return (
- <form
- className="flex gap-2 items-center mt-4 pt-4"
- style={{ borderTop: "1px solid var(--border)" }}
- onSubmit={(e) => {
- e.preventDefault();
- const l = label.trim() || kind;
- onAdd(kind, l);
- setLabel("");
- }}
- >
- <span className="text-xs uppercase tracking-widest" style={{ color: "var(--text-muted)" }}>
- Add
- </span>
- <select value={kind} onChange={(e) => setKind(e.target.value)} className="text-sm px-2 py-2" style={inputStyle}>
- {SOURCE_KINDS.filter((k) => k !== "todos").map((k) => (
- <option key={k} value={k}>
- {k}
- </option>
- ))}
- </select>
- <input
- value={label}
- onChange={(e) => setLabel(e.target.value)}
- placeholder="Label (e.g. My Feeds)"
- className="flex-1 px-3 py-2 text-sm"
- style={inputStyle}
- />
- <button type="submit" className="px-3 py-2 text-sm" style={inputStyle}>
- Add source
- </button>
- </form>
);
}