desktop: five sections by job (Release, Store page, Revenue, Players, Health), Game services view, sidebar by attention

- app view: 12 tabs become 5 sections, each with a header (what it is for, the one next step from state
  already read) and its sub views in a segmented control; 1-5 jump, Ctrl+Shift+[ ] cycle, Ctrl+K and the
  context menu list sections and sub views; old tab ids, section and section/sub links all resolve
- Players > Game services (games only): both stores, one row per leaderboard / achievement with its
  languages and each store's state, Game Center locale map, Play API-disabled state with the console plan
- games status --json: items, localeMap, playApiDisabled; inspect summary: games
- sidebar: apps grouped by attention / in progress / live / not on a store, one status line each, studio
  filter, workspace as a footer icon row
- boot check opens places like a person (section, then sub view)
This commit is contained in:
CRTkafa 2026-10-24 13:01:50 +08:00
parent 98cd1d3c92
commit 0ef27cf2e4
22 changed files with 918 additions and 115 deletions

View file

@ -750,3 +750,21 @@ export function useRead<T>(target: string, method: string, o: { network: boolean
return { ...s, loading: !o.skip && (s.loading || st.busy), justNow: st.justNow, pausedUntil: st.pausedUntil, refresh };
}
/** The cached value of one read (and every later result of it), without ever starting a read: for summaries such as a
* section's "what's next" line that must not cost a store call. Null until the read ran once. */
export function useCachedRead<T>(target: string | null, method: string, args: Record<string, unknown> = {}): T | null {
const d = useData();
const argsKey = JSON.stringify(args);
const [v, set] = useState<T | null>(null);
useEffect(() => {
set(null);
if (!target) return;
let alive = true;
const a = JSON.parse(argsKey) as Record<string, unknown>;
api.cached<ToolResult>(method, target, a).then(c => { if (alive && c && c.value.data !== undefined && usable(method, c.value)) set(c.value.data as T); }, () => {});
const un = d.onRead(readKey(target, method, a), e => { if (e.kind === 'done' && usable(method, e.result)) set(e.result.data as T); });
return () => { alive = false; un(); };
}, [target, method, argsKey]); // eslint-disable-line react-hooks/exhaustive-deps
return v;
}