heartwood every commit a ring

Refresh docs, prune dead config, and tidy small code issues

6c7845ed by Isaac Bythewood · 23 days ago

Refresh docs, prune dead config, and tidy small code issues

- README: drop stale Vercel section, add features overview
- CLAUDE.md: fix _app.js wrapper list, replace uuid with crypto.randomUUID,
  point theme at globals.css, document components and reducer actions
- package.json: declare prop-types (was transitive-only), drop unused react-is
- Delete unused site.config.js (theme lives in globals.css)
- sitemap.xml: refresh lastmod and add /summary
- l10n.js: drop bogus propTypes, merge useContext calls
- time.js: stop wrapping hours at 60
- log.js: rename shadowed strings arg
- Dockerfile: add CMD so standalone docker run works
modified CLAUDE.md
@@ -19,14 +19,29 @@ There are no tests or linting configured in this project.**Framework:** Next.js (Pages Router) with React. Plain JavaScript (no TypeScript).**State management:** Single React Context + useReducer pattern in `components/context.js`. All app state (timer, log entries, notes, language) lives here and is persisted to localForage on every reducer action. The reducer handles actions like ADD_LOG, EDIT_LOG, REMOVE_LOG, NEW_TIMER, etc.**State management:** Single React Context + useReducer pattern in `components/context.js`. All app state (timer, pause state, log entries, note draft, language, selection, edit mode) lives here and is persisted to localForage on every reducer action. Actions include `ADD_LOG`, `ADD_MANUAL_LOG`, `IMPORT_LOG`, `EDIT_LOG`, `REMOVE_LOG`, `CLEAR_LOG`, `CLEAR_TAG`, `NEW_TIMER`, `PAUSE_TIMER`, `RESUME_TIMER`, `NOTE_UPDATED`, `SET_LANGUAGE`, `NEXT_LOG_ITEM`, `PREVIOUS_LOG_ITEM`, `SELECT_LOG_ITEM`, `TOGGLE_EDITION`, `LOCALDATA_READY`.**Pages** (`pages/`): index (timer), log, summary, about. `_app.js` wraps everything with ContextProvider, HotKeysMapping, L10n, Sidebar, and page transitions.**Pages** (`pages/`): index (timer), log, summary, about. `_app.js` wraps everything with `ContextProvider`, `HotKeysMapping`, `ToastContainer`, and `Sidebar`, plus route-level page transitions via `react-transition-group`. `L10n` (language picker) is only rendered on the About page, not globally.**Localization** (`l10n/`): Uses react-localization. Each component/page has a corresponding l10n file with strings for en, jp, and pl.**Components** (`components/`):- `timer.js` — timer display + note input + reset/pause/add buttons- `entry.js` — one log row, inline-editable via `react-hook-form`- `newEntryForm.js` — manual entry form with datetime-local inputs- `tagNoteInput.js` — text input with `#tag` autocomplete dropdown- `sidebar.js` — left nav with brand mark + page icons- `HotKeysMapping.js` — global hotkey handlers via `react-hotkeys`- `keyHelpOverlay.js` — `?` shortcut cheat-sheet modal- `l10n.js` — language `<select>` (used on About page)- `page.js` — shared `<Head>` + page wrapper**Styling:** CSS Modules (`styles/components/` and `styles/pages/`) plus a global stylesheet. Theme colors defined in `site.config.js`.**Localization** (`l10n/`): Uses `react-localization`. Each component/page has a corresponding l10n file with strings for en, jp, and pl.**Key libraries:** chart.js (summary charts), react-hook-form (entry editing), react-hotkeys (keyboard shortcuts), react-csv (export), react-toastify (notifications), uuid (entry IDs).**Styling:** CSS Modules in `styles/components/` and `styles/pages/`. Global styles and the full theme (CSS custom properties, palette, transitions, toast overrides) live in `styles/globals.css`.**Log entries** have: id, start, end, note, and tags (extracted from #hashtags in the note text).**Import/export** (`utils/importExport.js`): CSV, JSON, and Markdown export; CSV/JSON import with de-duplication and tag re-extraction. CSV cells are prefixed with `'` when they start with `=+-@` to neutralize formula-injection in spreadsheets.**IDs:** `crypto.randomUUID()` with a `Date.now()`-based fallback for environments without it (see `newId` in `context.js`).**Key libraries:** `chart.js` (summary charts), `react-hook-form` (entry editing), `react-hotkeys` (keyboard shortcuts), `react-csv` (CSV export), `react-toastify` (notifications), `react-transition-group` (page + entry transitions), `localforage` (IndexedDB persistence), `prop-types` (runtime prop checks).**Log entries** have: `id`, `start`, `end`, `note`, and `tags` (lowercased `#hashtag` words extracted from the note text).
modified Dockerfile
@@ -12,3 +12,5 @@ COPY . .RUN bun run next:buildUSER bunCMD ["bun", "next:start"]
modified README.md
@@ -31,6 +31,16 @@ For an overview of how to get this project running and why it's useful check outthe DB Tech video on it here: https://www.youtube.com/watch?v=woG6qOmxlOA## Features- **Timer** with pause/resume and `#hashtag` note support- **Log** with per-day grouping, tag filtering, inline edit, manual entry,  and import/export (CSV, JSON, Markdown)- **Summary** with charts for hours-per-tag and hours-per-day- **Keyboard shortcuts** — press `?` anywhere to see the full map- **Offline-first PWA** — installable, all data stays in IndexedDB## CloneFor any possible way of running Timelite yourself you'll need a copy of the
@@ -55,13 +65,9 @@ browser at `http://localhost:8000`.## ProductionYou can either push to [Vercel](https://vercel.com/) with an update to the`vercel.json` file to change the alias or install Docker and docker-compose anduse that on any server.With docker appending `-d` to the end after `up` will run this container indetached mode. We have `restart: unless-stopped` configured so on systemrestarts or crashes the container will start back up automatically.Install Docker and docker-compose, then run the container on any server.`restart: unless-stopped` is configured so the container comes back up aftersystem restarts or crashes. Appending `-d` runs it in detached mode:    docker-compose up -d
modified bun.lock
@@ -7,12 +7,12 @@        "chart.js": "^4.5.1",        "localforage": "^1.9.0",        "next": "^16.1.6",        "prop-types": "^15.8.1",        "react": "^19.2.4",        "react-csv": "^2.0.3",        "react-dom": "^19.2.4",        "react-hook-form": "^7.71.2",        "react-hotkeys": "^2.0.0",        "react-is": "^19.2.4",        "react-localization": "^2.0.6",        "react-toastify": "^11.0.5",        "react-transition-group": "^4.2.1",
@@ -146,7 +146,7 @@    "react-hotkeys": ["react-hotkeys@2.0.0", "", { "dependencies": { "prop-types": "^15.6.1" } }, "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q=="],    "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],    "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],    "react-localization": ["react-localization@2.0.6", "", { "dependencies": { "localized-strings": "^2.0.3" } }, "sha512-+UIJ8Dm/Bfdrz38u4d6rgW2Ip9TP9uetUp1WgTrWibgPoJXnclAgPafWL26NbBzuxmOxo9UIRqOKWY/MBJmNdQ=="],
@@ -165,7 +165,5 @@    "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],    "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],    "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],  }}
modified components/l10n.js
@@ -1,13 +1,11 @@import React, { useContext } from "react";import PropTypes from "prop-types";import { Context } from "./context";import styles from "../styles/components/l10n.module.css";const L10n = () => {  const { state } = useContext(Context);  const { dispatch } = useContext(Context);  const { state, dispatch } = useContext(Context);  return (    <select
@@ -24,8 +22,4 @@ const L10n = () => {  );};L10n.propTypes = {  setLanguage: PropTypes.func,};export default L10n;
modified package.json
@@ -9,12 +9,12 @@    "chart.js": "^4.5.1",    "localforage": "^1.9.0",    "next": "^16.1.6",    "prop-types": "^15.8.1",    "react": "^19.2.4",    "react-csv": "^2.0.3",    "react-dom": "^19.2.4",    "react-hook-form": "^7.71.2",    "react-hotkeys": "^2.0.0",    "react-is": "^19.2.4",    "react-localization": "^2.0.6",    "react-toastify": "^11.0.5",    "react-transition-group": "^4.2.1"
modified pages/log.js
@@ -40,7 +40,7 @@ const dayKeyOf = (date) => {  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;};const dayLabelOf = (date, strings) => {const dayLabelOf = (date, l10nStrings) => {  const d = date instanceof Date ? date : new Date(date);  const today = new Date();  today.setHours(0, 0, 0, 0);
@@ -49,8 +49,8 @@ const dayLabelOf = (date, strings) => {    a.getMonth() === b.getMonth() &&    a.getDate() === b.getDate();  const yesterday = new Date(today.getTime() - 86400000);  if (sameDay(d, today)) return strings.today;  if (sameDay(d, yesterday)) return strings.yesterday;  if (sameDay(d, today)) return l10nStrings.today;  if (sameDay(d, yesterday)) return l10nStrings.yesterday;  return null;};
modified public/sitemap.xml
@@ -1,14 +1,18 @@<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">  <url>    <loc>https://timelite.bythewood.me/</loc>    <lastmod>2022-06-16</lastmod>    <lastmod>2026-04-18</lastmod>  </url>  <url>    <loc>https://timelite.bythewood.me/log</loc>    <lastmod>2022-06-16</lastmod>    <lastmod>2026-04-18</lastmod>  </url>  <url>    <loc>https://timelite.bythewood.me/summary</loc>    <lastmod>2026-04-18</lastmod>  </url>  <url>    <loc>https://timelite.bythewood.me/about</loc>    <lastmod>2022-06-16</lastmod>    <lastmod>2026-04-18</lastmod>  </url></urlset>
deleted site.config.js
@@ -1,15 +0,0 @@const theme = {  colors: {    bg: "#0E0D0A",    bgDeep: "#090806",    surface: "#13120E",    green: "#6B9E78",    greenBright: "#7DB88C",    amber: "#C9A84C",    terracotta: "#C47055",    text: "#DDD7CD",  },  breakpoint: "max-width: 1023.99px",};export { theme };
modified utils/time.js
@@ -6,7 +6,7 @@ const timeDiff = (time) => {const timeString = (timeDiff) => {  // timeDiff should be milliseconds  const timerTotal = [    (timeDiff / 1000 / 60 / 60) % 60, // Hours    timeDiff / 1000 / 60 / 60, // Hours (uncapped so long sessions don't wrap)    (timeDiff / 1000 / 60) % 60, // Minutes    (timeDiff / 1000) % 60, // Seconds  ];