feat(facts): non-repeating rotation and a bigger curated list #18

Merged
alex merged 3 commits from pi/issue17-1785399486606 into master 2026-07-30 11:31:13 +00:00
Contributor

Summary

Improves the daily horse-fact UX, which was "boring and repeating." Closes #17.

What changed

  1. No more repeats — facts are now drawn from a shuffle bag (FactDeck): the bot walks the entire list in random order before any fact can appear again. The rotation state is persisted (fact-deck.json), so a restart/redeploy never repeats a just-shown fact. This is the core fix for "repeating."
  2. Much larger list — the curated set grew from ~30 to ~70 verified facts, keeping things fresh for months.

Why no API source?

A research pass found no widely-available, reliable, horse-specific facts APIhorsefacts.org is a parked landing page, and the only free, key-less options are generic trivia APIs (catfact.ninja, uselessfacts.jsph.pl), neither of which is horse-themed. Rather than ship an opt-in layer onto a flaky third party, the curated deck stays the single source of truth: always available, no key, no network dependency, no new failure mode. (Consistent with the resilience-first design of the feed poller.)

Details

  • src/facts.tsFactDeck (shuffle bag) + FactDeckState, shuffle (Fisher-Yates). Removed the now-unused stateless pickRandomFact.
  • src/fact-store.ts — disk-backed store for the rotation state, mirroring GreetedNicksStore/FeedCheckpointStore (missing file = fresh start; malformed file = error).
  • src/config.tsFactsSettings + loadFacts (just the IRC_FACTS_STORE_PATH).
  • src/main.tscreateFactProvider (deck only) wired into the daily midnight scheduler.
  • Tests: the deck (no-repeat, reconciliation across list changes, round-trip), shuffle, and the store. Updated broadcastFact test for the new async signature.
  • README.md / .env.example documented.
  • Swept all sources to replace non-ASCII typographic punctuation (em/en dashes, arrows, >=) with ASCII equivalents. Emojis and the °/± signs in factual content are intentionally kept.

All checks pass: fmt, lint, check, 165 tests, Fallow dead-code (clean).

## Summary Improves the daily horse-fact UX, which was "boring and repeating." Closes #17. ### What changed 1. **No more repeats** — facts are now drawn from a **shuffle bag** (`FactDeck`): the bot walks the entire list in random order before any fact can appear again. The rotation state is persisted (`fact-deck.json`), so a restart/redeploy never repeats a just-shown fact. This is the core fix for "repeating." 2. **Much larger list** — the curated set grew from ~30 to ~70 verified facts, keeping things fresh for months. ### Why no API source? A research pass found **no widely-available, reliable, horse-specific facts API** — `horsefacts.org` is a parked landing page, and the only free, key-less options are *generic* trivia APIs (`catfact.ninja`, `uselessfacts.jsph.pl`), neither of which is horse-themed. Rather than ship an opt-in layer onto a flaky third party, the curated deck stays the single source of truth: always available, no key, no network dependency, no new failure mode. (Consistent with the resilience-first design of the feed poller.) ### Details - `src/facts.ts` — `FactDeck` (shuffle bag) + `FactDeckState`, `shuffle` (Fisher-Yates). Removed the now-unused stateless `pickRandomFact`. - `src/fact-store.ts` — disk-backed store for the rotation state, mirroring `GreetedNicksStore`/`FeedCheckpointStore` (missing file = fresh start; malformed file = error). - `src/config.ts` — `FactsSettings` + `loadFacts` (just the `IRC_FACTS_STORE_PATH`). - `src/main.ts` — `createFactProvider` (deck only) wired into the daily midnight scheduler. - Tests: the deck (no-repeat, reconciliation across list changes, round-trip), shuffle, and the store. Updated `broadcastFact` test for the new async signature. - `README.md` / `.env.example` documented. - Swept all sources to replace non-ASCII typographic punctuation (em/en dashes, arrows, `>=`) with ASCII equivalents. Emojis and the `°`/`±` signs in factual content are intentionally kept. All checks pass: `fmt`, `lint`, `check`, 165 tests, Fallow dead-code (clean).
feat(facts): non-repeating rotation, bigger list, optional API source
All checks were successful
PR / fallow (pull_request) Successful in 14s
PR / deno (pull_request) Successful in 41s
b816528035
Co-authored-by: alex <alex@forge.l3x.in>
Owner

/pi fair enough, if there's no good webservice to be used no need to implement that part: keep all the rest, remove "Optional API source" code/docs, push to the PR.

Also, take the chance to cleanup all sources from ambiguous UTF chars like U+2018 or similar

/pi fair enough, if there's no good webservice to be used no need to implement that part: keep all the rest, remove "Optional API source" code/docs, push to the PR. Also, take the chance to cleanup all sources from ambiguous UTF chars like U+2018 or similar
pi changed title from feat(facts): non-repeating rotation, bigger list, optional API source to feat(facts): non-repeating rotation and a bigger curated list 2026-07-30 11:14:06 +00:00
Per review, remove the optional HTTP fact provider (code, config, tests,
docs) since no reliable horse-specific API exists. Keep the non-repeating
shuffle-bag rotation, the disk-backed state store, and the expanded curated
list.

Also sweep all sources for ambiguous non-ASCII typographic punctuation:
em/en dashes -> -- / -, right arrows -> ->, and >= -> >=. Emojis and the
degree/plus-minus signs in factual content are intentionally kept.

All checks pass: fmt, lint, check, 165 tests, Fallow (clean).

Co-authored-by: alex <alex@forge.l3x.in>
Author
Contributor

Let me check the fallow config and scan all source files for non-ASCII typographic characters.Let me look at the pre-PR baseline to understand what existed before this commit.I now understand the full picture. Let me make the edits: remove the optional API source from all files, then do a UTF cleanup pass across all sources. Starting with the source/test files (independent edits across files).Now the docs — .env.example and README.md:The whole README edit call failed. Let me read the exact table rows and redo all README edits.Now the multi-line API section in README (contains an em-dash, so I'll use Python to avoid byte-reproduction issues), plus a global UTF normalization across all sources:Now let me scan every non-ASCII codepoint across all sources to decide the cleanup precisely:Clear inventory. I'll normalize the genuinely typographic/editorial non-ASCII punctuation (dashes, arrows, ) to ASCII across all sources, while keeping intentional symbols (emojis, the ° in temperature facts, the ± math sign). Let me apply it:Only intentional symbols remain. Now let me verify everything compiles, tests pass, and lint/fmt/fallow are clean:165 tests pass. Now fmt check and Fallow dead-code analysis:Formatting needs applying (import collapse + markdown reflow from the dash→-- width changes). Let me run deno fmt:All green: check, lint, fmt, Fallow (0 issues), and 165 tests pass. Let me do a final review of the key changed files:No leftover API references. Final comprehensive verification:All checks green (check, lint, fmt, 165 tests, Fallow clean). Let me view the final README daily-facts section once more to confirm it reads well, then push to the PR:Everything reads cleanly. Now let me commit and push to the PR, updating the title and description to reflect the removed API source:The PR has been updated. Let me confirm the CI status:Done. Pushed to PR #18 with the optional API source removed and the UTF cleanup applied. Summary of what changed:

Removed the "Optional API source" entirely (no reliable horse-specific API exists, so the curated deck is now the single source of truth):

  • src/facts.ts — dropped fetchApiFact, FactFetcher, FactFetchResponse; kept FactDeck, shuffle, HORSE_FACTS, formatFact.
  • src/config.tsFactsSettings is now just storePath; removed IRC_FACTS_API_URL / IRC_FACTS_API_USER_AGENT parsing and the DEFAULT_FACT_API_USER_AGENT constant.
  • src/main.tscreateFactProvider(deckStore) now draws only from the deck; removed the fetchApiFact/FactsSettings imports and API-related comments.
  • Tests — removed the 8 fetchApiFact cases and the 2 API-related config cases; kept the deck/shuffle/store coverage.
  • .env.example / README.md — dropped the API docs, config rows, and the "Sourcing facts from an API" section.

Everything else (non-repeating shuffle-bag rotation, disk-backed FactDeckStore, ~70-fact list, async broadcastFact) is untouched.

Cleaned up ambiguous non-ASCII characters across all sources:

  • (em dash) → --, (en dash) → -, (arrow) → ->, >=, plus smart-quote/ellipsis mappings for safety.
  • Intentionally kept: emojis (🐴/📰/🚬) and the °/± signs inside factual content, which have no clean ASCII equivalent. Say the word if you'd prefer those normalized too.

All local checks pass: fmt, lint, check, 165 tests, Fallow dead-code (clean). PR title and description were updated to drop the API mention and note the cleanup.


View action run | Model: zai/glm-5.2 (thinking: max) | Time: 15m 25s | Tokens: 1.8M | Pi SDK v0.81.1 | Action v2.26.0-develop (develop @ 4387e26)

Let me check the fallow config and scan all source files for non-ASCII typographic characters.Let me look at the pre-PR baseline to understand what existed before this commit.I now understand the full picture. Let me make the edits: remove the optional API source from all files, then do a UTF cleanup pass across all sources. Starting with the source/test files (independent edits across files).Now the docs — `.env.example` and `README.md`:The whole README edit call failed. Let me read the exact table rows and redo all README edits.Now the multi-line API section in README (contains an em-dash, so I'll use Python to avoid byte-reproduction issues), plus a global UTF normalization across all sources:Now let me scan every non-ASCII codepoint across all sources to decide the cleanup precisely:Clear inventory. I'll normalize the genuinely typographic/editorial non-ASCII punctuation (dashes, arrows, `≥`) to ASCII across all sources, while keeping intentional symbols (emojis, the `°` in temperature facts, the `±` math sign). Let me apply it:Only intentional symbols remain. Now let me verify everything compiles, tests pass, and lint/fmt/fallow are clean:165 tests pass. Now fmt check and Fallow dead-code analysis:Formatting needs applying (import collapse + markdown reflow from the dash→`--` width changes). Let me run `deno fmt`:All green: `check`, `lint`, `fmt`, Fallow (0 issues), and 165 tests pass. Let me do a final review of the key changed files:No leftover API references. Final comprehensive verification:All checks green (check, lint, fmt, 165 tests, Fallow clean). Let me view the final README daily-facts section once more to confirm it reads well, then push to the PR:Everything reads cleanly. Now let me commit and push to the PR, updating the title and description to reflect the removed API source:The PR has been updated. Let me confirm the CI status:Done. Pushed to PR #18 with the optional API source removed and the UTF cleanup applied. Summary of what changed: **Removed the "Optional API source" entirely** (no reliable horse-specific API exists, so the curated deck is now the single source of truth): - `src/facts.ts` — dropped `fetchApiFact`, `FactFetcher`, `FactFetchResponse`; kept `FactDeck`, `shuffle`, `HORSE_FACTS`, `formatFact`. - `src/config.ts` — `FactsSettings` is now just `storePath`; removed `IRC_FACTS_API_URL` / `IRC_FACTS_API_USER_AGENT` parsing and the `DEFAULT_FACT_API_USER_AGENT` constant. - `src/main.ts` — `createFactProvider(deckStore)` now draws only from the deck; removed the `fetchApiFact`/`FactsSettings` imports and API-related comments. - Tests — removed the 8 `fetchApiFact` cases and the 2 API-related config cases; kept the deck/shuffle/store coverage. - `.env.example` / `README.md` — dropped the API docs, config rows, and the "Sourcing facts from an API" section. Everything else (non-repeating shuffle-bag rotation, disk-backed `FactDeckStore`, ~70-fact list, async `broadcastFact`) is untouched. **Cleaned up ambiguous non-ASCII characters across all sources:** - `—` (em dash) → `--`, `–` (en dash) → `-`, `→` (arrow) → `->`, `≥` → `>=`, plus smart-quote/ellipsis mappings for safety. - **Intentionally kept:** emojis (`🐴`/`📰`/`🚬`) and the `°`/`±` signs inside factual content, which have no clean ASCII equivalent. Say the word if you'd prefer those normalized too. All local checks pass: `fmt`, `lint`, `check`, 165 tests, Fallow dead-code (clean). PR title and description were updated to drop the API mention and note the cleanup. --- [View action run](https://forge.l3x.in/alex/irc-greeter-bot/actions/runs/75) | Model: zai/glm-5.2 (thinking: max) | Time: 15m 25s | Tokens: 1.8M | Pi SDK v0.81.1 | Action v2.26.0-develop (develop @ 4387e26)
ok
All checks were successful
PR / deno (pull_request) Successful in 22s
PR / fallow (pull_request) Successful in 28s
e1b48a356e
alex merged commit b9064e4ca9 into master 2026-07-30 11:31:13 +00:00
alex deleted branch pi/issue17-1785399486606 2026-07-30 11:31:13 +00:00
Sign in to join this conversation.
No reviewers
No labels
dependencies
No milestone
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
alex/irc-greeter-bot!18
No description provided.