Files
website/src/hooks
mroxso adc65f9819 feat: Spells app for saved, shareable Nostr queries (grimoire kind 777) (#33)
* feat: Spells app for saved, shareable Nostr queries (grimoire kind 777)

The original feedback mentioned "grimoire spells" — traced to
github.com/purrgrammer/grimoire, a third-party Nostr client with its
own draft NIP for kind 777 "Spell" events: a REQ filter (kinds,
authors, one tag filter, limit, time window) encoded as portable,
shareable tags, with $me/$contacts runtime variables and relative
timestamps ("7d", "now").

- src/hooks/useSpells.ts implements that draft NIP as-is (same tags,
  same variables, same relative-time grammar) rather than a
  reinterpretation, so a spell saved here round-trips with Grimoire.
- src/apps/spells: browse "My Spells" / "Discover", build one with
  NewSpellForm, and Run it on demand against the resolved filter,
  rendering kind-1 results with NoteCard.
- Only the "Spell" half is implemented; "Spellbook" (kind 30777,
  saved window layouts) is left as a documented follow-up.

Closes #24

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

* fix: address review feedback on the Spells app

Per review:
- Scope is now derived (forced to "discover" when signed out) rather
  than stored as the requested value directly, matching the Feed
  app's pattern — signing out mid-session can no longer leave "My
  Spells" selected.
- resolveSpellFilter() now validates a spell's tag-filter letter
  (single a-zA-Z char) before using it as a "#<letter>" filter key,
  and clamps limit to [1, 500] instead of trusting a relay-sourced
  spell's number outright — a malformed or hostile spell can no
  longer produce a "#undefined" filter key or an enormous/NaN/zero
  limit. Added regression tests for all of these.
- NewSpellForm's Field now renders a real <label htmlFor> connected
  to each input's id (via useId()), and the Authors button group
  moved to a <fieldset>/<legend> instead of a label sitting over
  unrelated buttons — screen readers can now name every control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

* fix: validate spell tag filter/limit at write time, and hide malformed badges

Per review:
- encodeSpellTags() now validates the tag filter's letter and the
  limit before writing them, instead of only resolveSpellFilter()
  catching bad values on Run — a spell authored through this app can
  no longer save a filter it will silently fail to apply later.
  Exported isValidTagLetter() so both sides share one definition of
  "valid."
- The spell detail view's tag-filter badge now hides itself for a
  malformed tag filter (e.g. from a relay-sourced spell this app
  didn't author) instead of rendering "#undefined:" or similar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

* fix: reject non-integer/negative spell kinds, de-flake a timing test

Per remaining "previously missed" findings:
- parseSpell() now requires k tags to be non-negative integers
  (Number.isInteger && >= 0), not just finite — a relay-sourced spell
  claiming kind "1.5" or "-1" no longer passes through into a
  malformed filter.
- The resolveTimestamp wall-clock tests asserted toBeCloseTo a single
  captured `now`, which a slow runner or timing skew between the two
  Date.now() calls could flake. Replaced with a before/after range
  assertion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYiUtZMQeA5RHggQw73wto

---------

Co-authored-by: highperfocused <highperfocused@pm.me>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 18:55:22 +02:00
..
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00
2026-09-06 11:37:43 +02:00
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00
2025-12-27 21:42:31 +01:00
2025-12-27 21:42:31 +01:00
2026-09-06 11:37:43 +02:00