Every view (why:, cat:, diff:, spot:) emits the same {uri, text, toks} hunk stream, so the locator/search layer must consume tokenized hunks, not raw bytes. Hidden link toks — U (click-target URI bytes) and O (why's origin-commit link) — are zero-width and must be invisible to matching AND to line/col math. Supersedes SPOT-001 (grammar re-ruled by gritzko 2026-07-18).
why:/cat:/diff: fragment scoping, the spot view rebuild, the pager's in-view /jump — all funnel into one resolver.shared/spot.js: parse a fragment locator, resolve it over one or many hunks, clip sub-hunks. Fragment grammar (ruling, 2026-07-18):
#L123, #L123-160 — line / line range (the C HUNKu8sMakeURI #L42 convention);#needle — substring (CAPOGrep twin, the fallback);#/re/ — regex (CAPOPcreGrep twin);#'tok pattern' — structural SPOT (a-z bind one token, A-Z a block, two-space gap).L-prefixed lines keep the namespaces disjoint; a bare hex-looking needle loses to the hashlet.views/spot/match.js matchers; needle tokenizing rides tok.parse — no manual parsing beyond the locator grammar.API sketch (C twins: SPOTTokenize, SPOTInit/SPOTNext, SPOTReplace, CAPOGrep/CAPOPcreGrep, HUNKu32sClip, grepCtx):
parse(frag) → typed locator (line | substr | regex | spot).compile(loc, ext) — per-syntax needle prep; tokenizes 'pattern' with the hunk's own lexer, so one locator works across .c/.js/.mkd hunks; m.next(hunk) → {lo, hi, line, col} | null, SPOTNext-style.find(loc, hunk) — one hunk: compile against hunk.ext, drain next() → all spans ascending; callers pick first/nearest/all.scan(loc, hunks) — many hunks: lazy, yields {hunk, spans}, recompiles only on ext change (mixed-language result sets).clip(hunk, lo, hi) — HUNKu32sClip twin: token-edge snap, a hidden U/O sticks to its visible predecessor (never split a [visible][U/O] pair); line numbers stay absolute (pre-clip).window(loc, hunk, nctx) — find + merge ±nctx line windows (grepCtx) + clip → subhunk — the one-call view helper.fromFile(bytes, ext) — tok.parse wrapper for raw-file callers; views pass hunks as-is.replace(loc, hunk, tmpl) — SPOTReplace twin, structural locators only.replace ships literal-span only: true SPOTReplace bind back-substitution needs shared/match.js to expose its a-z/A-Z binds (today spans only) — follow-up ticket candidate.x) — put can't stage removals, the commit must capture them.SPOT-002: shared/spot.js hunk locators + lift matchers