still-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 0fa0e84d537b
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone a8c677472ab5691741798a3d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/lib/store.ts
// Device storage: small JSON values in localStorage, shared live between components.
import { useSyncExternalStore } from "react";

const listeners = new Map<string, Set<() => void>>();
const cache = new Map<string, unknown>();

export function read<T>(key: string, fallback: T): T {
  if (cache.has(key)) return cache.get(key) as T;
  let value = fallback;
  try {
    const raw = localStorage.getItem(key);
    if (raw != null) value = JSON.parse(raw) as T;
  } catch { /* storage blocked or bad JSON: use the fallback */ }
  cache.set(key, value);
  return value;
}

export function write<T>(key: string, value: T) {
  cache.set(key, value);
  try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* storage full or blocked: keep it for this visit */ }
  listeners.get(key)?.forEach((l) => l());
}

export function useStored<T>(key: string, fallback: T): [T, (v: T) => void] {
  const value = useSyncExternalStore(
    (l) => {
      const set = listeners.get(key) ?? new Set();
      set.add(l);
      listeners.set(key, set);
      return () => set.delete(l);
    },
    () => read(key, fallback),
  );
  return [value, (v: T) => write(key, v)];
}

export type Settings = {
  voice: string;
  rate: number;
  bell: "bowl" | "bell" | "chime";
  tones: boolean;
  vibrate: boolean;
  words: boolean;
};

export const DEFAULT_SETTINGS: Settings = { voice: "", rate: 0.85, bell: "bowl", tones: true, vibrate: true, words: true };

export function settings(): Settings {
  return { ...DEFAULT_SETTINGS, ...read<Partial<Settings>>("still.settings", {}) };
}

export function useSettings(): [Settings, (patch: Partial<Settings>) => void] {
  const [raw] = useStored<Partial<Settings>>("still.settings", {});
  return [{ ...DEFAULT_SETTINGS, ...raw }, (patch) => write("still.settings", { ...read<Partial<Settings>>("still.settings", {}), ...patch })];
}

Change history

Link to OpenApps pages and add a contribution strip
0fa0e84d53 · 19 hours ago
Bind the agent address in the policy
6f3baa1c72 · 1 day ago
Add favicon.ico so browsers that ask for it stop getting a 404
1a004fe0e3 · 1 day ago
Bounded source sync
afe642fe50 · 2 days ago
Bounded source sync
2c581f55ea · 2 days ago
Bounded source sync
375934c28b · 2 days ago
Bounded source sync
74ff37d51a · 2 days ago