feat(prefs): ask which view to open on, and remember the answer
`view` was component state, so a reader who preferred the timeline was put back on the list by every reload — and which view opened at all had been decided for them twice over: the PRD said calendar, the app shipped the list. Neither was the reader's answer. So the first run asks, and the tabs write to `prefs` from then on. The question ships pre-answered with the list, because a reader cannot choose between two layouts they have not seen and that is the one that answers "what expires next" in a look — and each option is drawn rather than described, for the same reason. The screen says where to change it afterwards, since the tabs are small text in a corner. Adds `view` to the prefs key space. Additive and defaulted, so an existing reader's stored prefs open exactly where they did before. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
41046899f9
commit
7804a08863
+8
-6
@@ -17,7 +17,7 @@ import { useAppUpdate } from "./state/useAppUpdate.ts";
|
||||
import { useMarkSet } from "./state/useMarkSet.ts";
|
||||
import { useProgress } from "./state/useProgress.ts";
|
||||
import { useDailyLog, type DailyLogMap } from "./state/useDailyLog.ts";
|
||||
import { usePrefs } from "./state/usePrefs.ts";
|
||||
import { usePrefs, type View } from "./state/usePrefs.ts";
|
||||
import { useCustom } from "./state/useCustom.ts";
|
||||
import { compareRows, SORT_MODES, type Activity, type SortMode } from "./state/sort.ts";
|
||||
import {
|
||||
@@ -38,8 +38,6 @@ import {
|
||||
} from "../shared/custom.ts";
|
||||
import { metaFor } from "../shared/games.ts";
|
||||
|
||||
type View = "soon" | "timeline";
|
||||
|
||||
/**
|
||||
* How many deadlines the headline carries.
|
||||
*
|
||||
@@ -83,7 +81,6 @@ function useNow(intervalMs = 1000): number {
|
||||
|
||||
export function App() {
|
||||
const [state, setState] = useState<FeedState>({ status: "loading" });
|
||||
const [view, setView] = useState<View>("soon");
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
// The event most recently ignored, so it can be put back without hunting for
|
||||
// a row that just disappeared.
|
||||
@@ -91,6 +88,10 @@ export function App() {
|
||||
const now = useNow();
|
||||
const online = useOnline();
|
||||
const { prefs, update, toggleGame } = usePrefs();
|
||||
// Their answer from the first run, or their last tap on the tabs. Reading it
|
||||
// from `prefs` is what stops a reload putting a timeline reader back on the
|
||||
// list they did not choose.
|
||||
const view = prefs.view;
|
||||
const ignored = useMarkSet(KEYS.ignored);
|
||||
const prog = useProgress();
|
||||
const daily = useDailyLog();
|
||||
@@ -292,10 +293,11 @@ export function App() {
|
||||
<Shell>
|
||||
<Welcome
|
||||
available={games}
|
||||
onConfirm={(chosen) =>
|
||||
onConfirm={(chosen, chosenView) =>
|
||||
update({
|
||||
onboarded: true,
|
||||
hiddenGames: games.filter((g) => !chosen.includes(g)),
|
||||
view: chosenView,
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -338,7 +340,7 @@ export function App() {
|
||||
key={id}
|
||||
role="tab"
|
||||
aria-selected={view === id}
|
||||
onClick={() => setView(id)}
|
||||
onClick={() => update({ view: id })}
|
||||
className={`rounded-[6px] px-2.5 py-1.5 text-xs font-medium transition-colors ${
|
||||
view === id ? "bg-raised text-ink" : "text-faint hover:text-muted"
|
||||
}`}
|
||||
|
||||
Reference in New Issue
Block a user