feat: on-chain multi-tag directory filter (ADR-0048) + Overview pane, with explorer consuming it#27
feat: on-chain multi-tag directory filter (ADR-0048) + Overview pane, with explorer consuming it#27JamesCarnley wants to merge 95 commits into
Conversation
Capture the v1 design for the markdown 'Overview' pane in the explorer debug UI: system-tagged README.md resolved per lens, assemble-not-adopt react-markdown stack with sanitize-last, byte-sniffing + size cap, blocked external images, deferred contentHash/efs-link work with reserved seams. View-only, client-only; no contract/schema/ADR changes. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Keep EFS machinery (mirror/router fetch, lens/tag/readme resolution, content hashing) thin and delegate-ready for the in-progress SDK; build only client-only concerns (renderer, sniffing, layout, toggle) fully. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Fix critical feasibility bug (system tag must target the anchor and use a dedicated resolver, not FileBrowser's DATA-targeted matchesUID/dataUIDMap). Harden security: explicit protocol allowlist, strip img in-schema, safe download card, behavior:wrap anchors, node-count/depth guard, inert relative links. Pin multi-lens + pick->fetch->sniff order. Correct test runner (node --test). Add demo-seed step for system-tagged READMEs. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
SEC-1/UX-6 validate the independent contentType sniff; DX-2 -> shape the fetch util to the SDK's planned fetch(ref)/hashContent signature; UX-5 noted as SDK read-path (deferral consistent). Verified schema-freeze does not touch the reused debug-UI files, so building on main + later rebase is trivial. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
16 bite-sized TDD tasks: pure-logic units (sniff/select/download-name/ schema) under node --test, thin EFS seams (fetch util, system-tag resolver, useItemOverview), MarkdownView + OverviewPane, layout + hidden-files toggle, demo-seed, e2e verification. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…en tests The noControl regex used embedded invisible Unicode literal bytes ([\x00-\x1f\x7f-\x9f]) which weren't auditable. Rewrote it using explicit \uXXXX escape sequences for all ranges in both the noBidi and noControl steps. Replaced the vacuous "strips control chars" test (which called safeDownloadName on plain ASCII "abc.txt" — a no-op) with: - A test that embeds U+00B7 (middle dot) to confirm non-ASCII non-word chars are replaced with "_" while hyphens survive. - A versioned-filename test confirming "v1.0.0-beta.tar.gz" is preserved. Added dedicated magic-number tests to sniff.test.ts (JPEG, GIF, ZIP, gzip) so a refactor cannot silently break binary detection. Added overview.md > about.md precedence test to selectOverview.test.ts. All 46 tests pass; check-types clean. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the weak substring check on javascript:/data: with a positive href-absent assertion. Add six new vectors: mixed-case/obfuscated protocols, style/base/formaction tags, protocol-relative href passthrough (documented behavior), task-list checkboxes, table-cell alignment, and an autolink-heading wrap-anchor round-trip through rehypeSanitize. Fix the JSDoc clobber description to match the real defaultSchema value. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Pure async router-fetch (SSTORE2 + mirror) parameterized for reuse by the Overview hook; cancellation + setState stay in FileBrowser. Signature shaped toward the planned SDK fetch(ref) (holistic-review DX-2).
…t anchorSchema A README is a dataSchema child; listing/tagging by anchorSchema never finds it. Verified vs EFSFileView.getDirectoryPageBySchemaAndAddressList phase-1 semantics.
Verified vs EFSFileView phase-1 listing: a README is a dataSchema child; the design's anchorSchema framing was wrong. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Wire the already-built OverviewPane into ExplorerClient as a middle column between the topic tree and the file browser. Pulls EFSFileView, EdgeResolver, and EFSRouter deployed-contract infos alongside the existing Indexer/EFSSortOverlay reads and threads them plus the in-scope anchor/lens/path values into the pane. Rendered only when there is no path error; the pane returns null when the directory has no Overview, so the layout falls back to two panes. Wrapped in hidden lg:block to mirror the tree aside's narrow-screen collapse. Permanence-tier: Ephemeral Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adds a Show hidden toggle to the explorer that filters out child anchors tagged `system` by any active lens (the /tags/system set) from the directory grid by default. State lives in ExplorerClient, persists in localStorage (efs.showSystemFiles), and is threaded to FileActionsBar (the toggle control) and FileBrowser (the filter). FileBrowser resolves the system anchor-set via resolveSystemAnchorSet keyed on (currentAnchorUID, lenses, dataSchema) and applies it as a final independent visibleItems pass downstream of the existing descriptive-label tag filter — it does not touch drawerTagFilters / matchesUID. sortedItems, fileItems, the grid, and the empty-state read visibleItems; the load-more page heuristic intentionally stays on the raw fetched page length so hiding system files can't suppress paging. Permanence-tier: Ephemeral Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Tested-by: Claude Opus 4.8 <noreply@anthropic.com>
Add an idempotent, localhost-only, fail-soft seed extension that creates the `/tags/system` definition anchor and three `system`-tagged READMEs so the explorer's Overview pane has data to render: a folder README (`/docs/README.md`), a file-anchor README (under `/docs/readme.txt`), and an address-container README (under the demo lens's address root, via the ANCHOR recipient-fallback path). READMEs carry real on-chain bytes (SSTORE2 data contract + MockChunkedFile wrapper + web3:// mirror) so the Overview pane can fetch and render the markdown back through the router — an unreachable HTTPS placeholder would render nothing. All EAS work reuses the existing seed helpers (makeAnchor/makeData/makeMirror/makePin/makeTag/makeProperty); only the SSTORE2 deploy + chunked wrapper are new. Each README and system TAG is guarded (hasActivePlacement / hasActiveTagFromAny) so re-runs are a clean no-op; verified on a Sepolia fork (create on first run, skip on re-run). The system TAG is `definition=/tags/system anchor, refUID=README ANCHOR uid, weight=1`. Verified empirically that an anchor-targeted TAG files under the target anchor's EAS schema (anchorSchemaUID), NOT dataSchemaUID: `getActiveTargetsByAttesterAndSchema(systemDef, attester, anchorSchemaUID)` returns the README anchor uids while the dataSchemaUID query returns []. The client's resolveSystemAnchorSet currently queries the dataSchemaUID bucket, so it won't find these — flagged as a Tier-2 client bug in docs/QUESTIONS.md (the seed shape is correct; the client query bucket is the fix). Permanence-tier: Ephemeral Refs: ADR-0033, ADR-0041 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Tested-by: Claude Opus 4.8 <noreply@anthropic.com>
… listing Anchor-targeted TAGs bucket under the target anchor's EAS schema (anchorSchemaUID), verified on fork; README *listing* stays dataSchema. Two distinct schemas. Resolves QUESTIONS.md bucket entry.
The init prefix is 12 bytes (PUSH2+imm = 3, then 9), but CODECOPY copied from offset 0x0b (11), shifting the runtime: deployed code became 0xf3 0x00 content[..-1]. The router skipped only 0xf3, leaving a leading NUL + truncated content, so the client correctly sniffed it as binary. Offset 0x0c makes the on-chain README bytes exact. Client code was correct. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Collapsing now shrinks the pane to a w-9 rail with a chevron toggle («/») instead of leaving an empty w-96 shell, returning the width to the file list. Icon-based, matches the tree's collapse affordance. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Drop the index/overview/about precedence list and markdown-ish fallback. The overview is the system-tagged child named readme.md (case-insensitive), first lens with one, else no pane. The system tag stays as the general hide-bucket for keeping the file list clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Anchor names are case-sensitive on-chain, so README.md / readme.md / ReadMe.md are distinct anchors. Case-insensitive matching conflated them and was ambiguous when casings coexist. Match the one canonical name (README.md) exactly. Seed already uses README.md; demo unaffected. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…verview Listing is data-schema (files); selection is exact README.md, not a precedence list. Comment-only. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
EFS review agents share one GitHub account and some post malformed inline comments (wrong speaker prefix, non-native review, findings only in a review body). On PR #27 that caused Gemini's ~11 findings to be missed until flagged by hand, because the dev side reacted to notifications instead of enumerating. Adds scripts/pr-review-intake.sh (yarn pr:intake <PR>): a format-agnostic dev-side intake that enumerates EVERY feedback shape and flags what's unaddressed — - review threads (every inline comment becomes a thread with isResolved, however posted) via paginated GraphQL, - review summary bodies (body-only findings aren't thread-tracked), - timeline (PR-level) comments, and marks a thread NEEDS REPLY when its last comment is not a `[<model> · dev]` reply (login can't distinguish reviewer from dev under one shared account, so the `· dev` prefix does). Exits non-zero while any thread needs a dev reply, so it doubles as a pre-"done" gate. Manual on purpose — no CI comment-parsing. Validated against PR #27: surfaced all 19 unresolved threads incl. Gemini's malformed ones + an outdated thread + 6 review bodies. Designed and reviewed by expert subagents; applied their fixes (warn instead of silently swallowing a failed timeline fetch; strip tabs so a comment body can't corrupt the TSV row; doc wording). Also documents the deferred "Overview README anchor reachable before placement" finding (Codex) in FUTURE_WORK — name-only, self-healing, partly inherent to EAS UID non-precomputability; left as an unresolved review thread for James. docs(agent-workflow): adds a "Review intake" rule — run `yarn pr:intake <PR>` and enumerate before declaring review addressed; don't trust notifications/formatting. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Reviewed-by: Claude Opus 4.8 <noreply@anthropic.com> Permanence-tier: Durable
…ow (Codex P2)
Per James's call ("do the technically correct thing"). EFSIndexer sets
`_containsAttestations[anchorUID][creator]=true` at ANCHOR creation, and
getDirectoryPageFiltered phase 1 qualifies on that flag, so a freshly-created
README file slot lists before its placement PIN exists — and _isItemExcluded
(which reaches the DATA via the PIN) can't yet hide it against the pre-placement
system tag.
uploadOnchainFile now creates the file ANCHOR last — immediately before the
placement PIN, after the DATA is already system-tagged (beforePlacement) — instead
of up front. This shrinks the visible-but-unhidden window from ~6 txs to ~1. The
read-only reuse check stays at the top (it writes no on-chain state). Nothing
between the old and new anchor position references fileAnchorUID, so this is a pure
reorder (verified: nextjs check-types + lint clean).
The remaining 1-tx window (anchor mined, PIN not yet) is INHERENT: EAS UIDs aren't
precomputable, so the anchor and the PIN that references its UID can't be batched
atomically. Fully closing it would require a contract change (phase-1 qualifying
on active placement, not _containsAttestations), which alters Durable listing
semantics for all files — documented in FUTURE_WORK, out of scope here.
NOTE: the live multi-tx Overview save flow was not re-exercised here (needs a
wallet); the change is a pure block-move within the existing sequence and should
be smoke-tested on next preview.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Suggested-by: ChatGPT Codex <noreply@openai.com>
Permanence-tier: Ephemeral
Refs: ADR-0048
…alk (Codex P2) Codex re-raised that uploadOnchainFile's ancestor-visibility walk would mis-tag a FILE anchor as a visible folder if `parentAnchorUID` were a file. Confirmed NOT reachable: uploadOnchainFile has a single caller (OverviewEditorModal) that passes `currentAnchorUID` — always the current folder/container; clicking a file opens a preview and never becomes currentAnchorUID. No runtime guard added because the only correct one needs each node's anchorType, and EFSIndexer cannot expose a getter for it (its address is baked into the schema UIDs — kernel immutability), so it would require a client-side EAS getAttestation + decode — disproportionate for a non-reachable path. Documented the invariant loudly inline at the walk + a conditional follow-up in FUTURE_WORK (add the guard if a per-file Overview is ever introduced). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral
JamesCarnley
left a comment
There was a problem hiding this comment.
[codex-gpt-5 · review]
Same-account advisory review: BLOCKING
Continued expert-subagent review on head 22c81a5. Blocking native threads are now open for:
- existing file-anchor Overview visibility walk, with seed-script instance added
- exclude-cap ordering dropping default
system/nsfwhides - Markdown heading hash links still targeting the wrong sanitized IDs
- review intake truncating body-only reviews to non-actionable snippets
- Overview modal close bypassing the Stop/cancellation path
- Explorer URL path double-decode / encoded-segment loss
I did not duplicate the resolved README-anchor timing thread because 22c81a5 materially improves that window and documents the remaining inherent one-transaction gap. Same-account note: this is a COMMENT review because all agents are using James's GitHub account; treat P2 threads as blocking until resolved, deferred, or overridden.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbb36ae2f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Expert-subagent verification of Codex's latest pass: 2 findings were already fixed (MarkdownView hash-link aad4879, exclude-cap aad4879); 4 were real — and the subagent caught that my earlier "ancestor walk not reachable" pushback was WRONG. Fixes: - Block Overview writes on file anchors (Codex P2 — the pushback was wrong). The walk IS reachable: a deep link like /explorer/docs/readme.txt resolves currentAnchorUID to a DATA-schema file leaf, and the Overview editor rendered on it; saving passed the file anchor as parentAnchorUID and the ancestor walk would tag it as a visible folder. ExplorerClient now tracks whether the last path segment resolved as a file leaf (currentIsFileLeaf) and gates overviewEditable = ... && !currentIsFileLeaf. Verified: editor affordance present on /docs (folder), absent on /docs/readme.txt (file). Corrected the now-wrong "not reachable" invariant note + FUTURE_WORK. - URL path double-decode (Codex P2). pathSegmentsFromPathname already decodes each segment; the resolver decoded AGAIN, so /explorer/10%25 threw URIError and a decoded value was stored in urlSegment (used raw in URLs). Decode once: name = segment (already decoded), urlSegment = encodeURIComponent(segment) for lossless round-trip. Verified /docs/readme.txt resolves with no path error. - Modal close during save bypassed cancellation (Codex P2). The header X called onClose without setting cancelledRef, so uploadOnchainFile kept broadcasting txs after the modal unmounted. X now routes through cancellation (cancelledRef = true when isSaving) before onClose. - pr:intake hid body-only findings (Codex P2 — on our own tool). It printed only the first 150 chars of each review body, so findings after a long intro (the multi-KB Gemini review body) were missed. Now prints the FULL body + author + review URL. Validated against PR #27. nextjs check-types + lint clean; intake bash syntax clean; browser-smoke-tested folder vs file-leaf navigation and the Overview gate. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Reviewed-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Tested-by: Claude Opus 4.8 <noreply@anthropic.com> Permanence-tier: Ephemeral Refs: ADR-0048
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 375f069a8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…E seed (Codex P2) Two follow-ups from Codex's pass on 375f069, both consequences of the prior anchor-reorder / seed-ordering work: - uploadOnchainFile: removed the checkCancelled() between the README ANCHOR creation and its placement PIN. Stopping there left a DATA-schema README slot indexed with no PIN, and the exclude predicate reaches the system-tagged DATA only via the PIN — so the default `system` rule couldn't hide that visible empty slot. The anchor→PIN pair is now atomic once the anchor is broadcast; the cancellation checkpoints are the beforePlacement hook (before the anchor) and after the PIN. - seed-impl: makeOnchainReadmeIfMissing gains a skipVisibilityWalk param, passed for the file-anchor README (/docs/readme.txt). Without it the ancestor walk tagged the FILE anchor with definition=dataSchemaUID, making EFSFileView phase 0 treat the file as a qualifying folder — duplicate cards + a folder-visible file. The README still renders via the exact-path Overview lookup, so the walk is just skipped for file-parented READMEs. nextjs + hardhat check-types & lint all clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
JamesCarnley
left a comment
There was a problem hiding this comment.
[codex-gpt-5 · review]
Same-account advisory review on head 882ccc7. I verified Claude's latest fixes with specialist subagents: the anchor→PIN non-cancellable change and the seed skipVisibilityWalk change are real improvements. I reopened the still-live native threads for Markdown hash targets, exclude-cap ordering, and the file-anchor README render path. One new P2 inline is below for deploy ordering.
Submitting as a COMMENT review because all agents are operating through James's GitHub account; treat unresolved P2 threads as blocking until fixed, explicitly deferred, or overridden.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 882ccc75f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…/system (Codex P2) On an unseeded chain (or a real deploy where the demo seed is skipped), /tags/system doesn't exist at mount, so FileBrowser's exclude resolver settles with excludeTagDefUIDs=[] (unfiltered). The first Overview save then creates /tags/system + system-tags the README via applySystemTag — but onOverviewSaved only bumped overviewRefreshKey, so the directory never re-resolved tagsRoot and the new system-tagged README stayed visible until a remount or tag-filter bump. Fix: onOverviewSaved now also bumps directoryRefreshKey, and FileBrowser's tagsRoot resolver re-runs on directoryRefreshKey (guarded by `if (tagsRoot) return` — once found it's permanent, so this only re-checks the absent case). When the save creates /tags/system, tagsRoot resolves → the exclude resolver re-runs (tagsRoot is its dep) → excludeTagDefUIDs picks up the system def → the directory re-queries filtered and hides the README. The excludesPending gate holds during the re-resolution, so no unfiltered flash. No effect on the seeded devnet (tagsRoot already resolves at mount). nextjs check-types + lint clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
…x P2) 10_seed_demo_tree's dependencies omitted EFSFileView, EFSRouter, and SortFunctions. Nothing else depends on those tags, so they weren't pulled into the seed's transitive closure — a `--tags SeedDemoTree` run could execute the seed before they deployed, and the seed's nonce-consuming txs would then shift their CREATE addresses (breaks the deterministic pin, ADR-0037). Enumerate all contract-deploying tags explicitly. Full-deploy order (filename 01→10) is unchanged, so the committed pin is unaffected (deploy-pin-check stays green). hardhat check-types + lint clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Durable Refs: ADR-0037
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9ae1728d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…erred from #27) James's decision (2026-06-14): the lens list answers "whose data?" — vitalik.eth = only vitalik's, me = only mine, EMPTY = all data from everyone. The system/nsfw exclusion (ADR-0048) and pagination still apply; empty only widens whose content. Too big for PR #27 (the on-chain tag-filter PR) and it supersedes ADR-0031/0039 (viewer sovereignty / systemLenses tail) + needs a contract path (EFSFileView treating an empty attesters[] as "all", still filtered + paged) that should be settled before the Sepolia freeze. Recorded as a LAUNCH_CHECKLIST Devnet/Frontend gate with the full design (ADR + contract + client wiring) in FUTURE_WORK. No non-address sentinel needed — the empty address[] is the signal. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Permanence-tier: Durable Refs: ADR-0031, ADR-0039, ADR-0048
…ed (Codex P2) Follow-on to a9ae172. On an unseeded chain the Overview save bumps directoryRefreshKey, which fired the parent-driven refetch immediately with excludeTagDefUIDs=[] — before the async tagsRoot re-resolution finished — briefly rendering the just-created, system-tagged README via the unfiltered read. The refetch effect now re-gates instead: when /tags is unresolved but excludes are expected (`!tagsRoot && drawerExcludeNamesKey !== ""`), it sets excludeResolved=false (holds the directory via excludesPending) and skips the immediate refetch. The tagsRoot resolver (also keyed on directoryRefreshKey) re-resolves the newly-created /tags/system, the exclude resolver re-runs on the tagsRoot change and resolves the system def, and the directory then fetches FILTERED via its depsKey — no unfiltered flash. Release is guaranteed: a successful Overview save always creates /tags/system (applySystemTag), so tagsRoot transitions null→set and re-triggers the resolver. Seeded chains are unaffected (tagsRoot already set → normal refetch). nextjs check-types + lint clean; /docs smoke-tested (README hidden, no regression). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1813c862d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…dex P2 ×2) #1 (FileBrowser): the exclude-def cap sliced the first 8 of an ALPHABETICALLY sorted name list, so 8+ user-added exclude tags sorting before nsfw/system would drop the safety defaults — un-hiding system/nsfw despite their drawer toggles. Now order the safety excludes (system, nsfw) FIRST before the MAX_EXCLUDE_TAGS slice, so they're never dropped. #2 (seed + ExplorerClient): file-level Overviews don't work end to end — the UI already gates creation off on file leaves, and the read path can't resolve them either: EFSRouter walks intermediate segments with generic resolvePath, which returns only generic (bytes32(0)) anchors, so the intermediate file anchor readme.txt 404s before the child README. Made Overviews folder-scoped: - removed the seeded /docs/readme.txt/README.md file-Overview (dead, unreachable data — verified pin-neutral: seed runs last, no contract addresses change); - the Overview pane no longer mounts/probes on file leaves (`!currentIsFileLeaf`), avoiding a guaranteed-404 router probe. Per-file Overviews (needs a router change to try DATA-schema on intermediate segments) documented in FUTURE_WORK as a pre-Sepolia option. Verified: nextjs + hardhat check-types & lint clean, docs:check clean; browser — Overview shows on /docs (folder), gated off on /docs/readme.txt (file leaf). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5b0e1232a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
JamesCarnley
left a comment
There was a problem hiding this comment.
[codex-gpt-5 · heartbeat review]
Same-account advisory review on head d5b0e12. The latest fixes cleared the deploy dependency, exclude-cap ordering, Overview-save re-resolve, and file-leaf pane probing concerns. Two P2s remain open as native threads: the Markdown heading hash target mismatch, and stale file-scoped Overview design/comments that still advertise unsupported behavior.
Submitting as a COMMENT review because all agents are operating through James's GitHub account; treat unresolved P2 threads as blocking until fixed, explicitly deferred, or overridden.
…ave (Codex P2) The real-deploy leak: 01_indexer creates /tags but NOT /tags/system (localhost seed only). So FileBrowser settles with excludeTagDefUIDs=[] (system def absent), the first Overview save creates /tags/system, but the prior fix only re-checked tagsRoot when /tags was ABSENT (`if (tagsRoot) return`) — with /tags already present, tagsRoot is unchanged, the exclude resolver never re-runs, and the new system README renders until remount. Root-caused the recurring races here to one shared signal (directoryRefreshKey) driving BOTH the parent refetch and the exclude re-resolution, racing in the same commit. Fix: split them. Overview saves now bump a dedicated `excludeRefreshKey` that drives ONLY the exclude resolvers (which set excludeResolved=false to HOLD the directory while resolving, then drive a FILTERED refetch via depsKey) — never the parent refetch. directoryRefreshKey is left to create/delete/list mutations, which don't touch /tags/system, so its re-gate hack is removed. - excludeRefreshKey re-runs the exclude resolver (re-resolves /tags/system even when tagsRoot is unchanged — the real-deploy case) and the tagsRoot resolver (for the bare-chain case where /tags itself was absent). - Reverted the a9ae172 directoryRefreshKey→tagsRoot coupling and the c1813c8 firstRefreshRun re-gate (both subsumed by the dedicated key). Verified: nextjs check-types + lint clean; /docs seeded path unaffected (README hidden, not stuck). The unseeded/first-Overview path is logic-traced (can't drive a multi-tx save in the preview); residual: a bare chain with NO /tags at all has a brief self-correcting flash on the very first Overview (non-deploy case). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
…sibilityWalk (Codex P2) Follow-up to the folder-scoped Overview decision (d5b0e12): - overview-pane-design.md: implementation note now marks the "any EFS item works / file anchor" claims (§Goal, §"Every item type works") as SUPERSEDED — Overviews are folder-scoped (file leaves can't be created or read; router resolves intermediate segments with generic resolvePath). Points at FUTURE_WORK for the router-change path. - seed-impl.ts: removed the now-dead `skipVisibilityWalk` param (its only caller — the file-anchor README — was removed in d5b0e12) and corrected the stale "three demo READMEs — on a folder, on a file anchor, ..." comment to two (folder + address). Prevents future seed/router work from reintroducing unreachable file-scoped README data. hardhat check-types + lint clean; docs:check clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b50646ea6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Same file-anchor mis-tag class as the Overview gate: on a file-leaf URL (/explorer/docs/readme.txt) currentAnchorUID is the DATA-schema file anchor, but the Add → Folder/File/List actions were still enabled. Creating a child there passes the file anchor into CreateItemModal, whose ancestor-visibility walk writes TAG(definition=dataSchemaUID, refUID=<file anchor>), so the parent folder's phase-0 query treats the file as a folder while phase 1 still returns it as a file. FileActionsBar now takes currentIsFileLeaf (threaded from ExplorerClient, same flag the Overview gate uses); the three create guards block when it's set (Open-a-folder notification, no modal). Verified in-browser: on /docs/readme.txt, Add → Folder opens no create modal. nextjs check-types + lint clean. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Suggested-by: ChatGPT Codex <noreply@openai.com> Permanence-tier: Ephemeral Refs: ADR-0048
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a26d99e800
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
JamesCarnley
left a comment
There was a problem hiding this comment.
[codex-gpt-5 · heartbeat review]
Same-account advisory review: BLOCKING
Converted one still-live, body-only security finding into a native file-scoped review thread so it has a Resolve button. GitHub rejected an exact line comment on the unchanged PDF iframe context line, so the thread is attached to packages/nextjs/components/explorer/FileBrowser.tsx at file scope. The three previously open P2 threads remain unresolved as well.
Addresses the two remaining open Codex P2 findings on PR #27. Problem 1 (uploadOnchainFile): after the placement PIN lands the file is already saved on-chain, but a cancellation from that point on (drawer/modal close, navigation) still threw UPLOAD_CANCELLED. Callers treat the throw as failure, so OverviewEditorModal skipped onSaved() and never bumped excludeRefreshKey — leaving a just-created system README (e.g. on a fresh chain) visible in the grid. Fix: the placement PIN is the commit point. Drop the post-placement checkCancelled(); the best-effort ancestor-visibility walk now swallows cancellation (stops early, logs) instead of re-throwing, so the function returns normally and the success path always runs. Problem 2 (FileBrowser): the inline and fullscreen PDF previews rendered untrusted mirror bytes via a blob: URL in an unsandboxed iframe. A blob: URL inherits the app origin, so embedded PDF JS (or spoofed content) ran same-origin. Fix: sandbox both iframes with allow-scripts and no allow-same-origin — the same opaque-origin model already used for the HTML preview. The PDF viewer (incl. pdf.js) still works; framed content can no longer reach the parent app's DOM / cookies / storage. Verified: yarn check-types (0), yarn lint (clean), yarn docs:check (OK). Permanence-tier: Ephemeral Refs: ADR-0038, ADR-0041 Suggested-by: Codex GPT-5 <noreply@openai.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… docs Addresses the last two open Codex P2/P3 threads on PR #27. MarkdownView heading links (P2): rehype-sanitize prefixes heading ids with `user-content-` (DOM-clobber defense) but leaves href fragments untouched, so the autolink `rehypeAutolinkHeadings` wraps around each heading ("#hello-world") pointed at the now-"user-content-hello-world" id and scrolled nowhere. Added `resolveHashHref` (and exported `CLOBBER_PREFIX`) in utils/markdown/schema.ts to re-point same-page fragments at the prefixed id; wired it into the `<a>` override. Fixes heading autolinks and author-written [x](#section) links. New unit tests prove raw-href != "#"+id and that the helper closes the gap. Stale file-Overview docs (P3): Overviews shipped folder-scoped, but live body instructions in overview-pane-design.md (the model paragraph, the demo-seed "file anchor", the manual-matrix "a file") and the makeOnchainReadmeIfMissing docstring still pointed future seed/router work at unreachable per-file README data. Marked the doc lines inline as superseded (it's a historical design record) and corrected the seed docstring to state parentUID is never a file anchor. Verified: yarn test (78/78), check-types (0), lint (clean), hardhat lint (0), docs:check (0). Permanence-tier: Ephemeral Refs: ADR-0033, ADR-0048 Suggested-by: Codex GPT-5 <noreply@openai.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d8b182345c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Addresses a new Codex P2 on PR #27. The Overview README seed created the non-revocable DATA-schema anchor early — about five txs (DATA + contentType + mirror + system TAG) before the placement PIN. Because EFSFileView phase 1 lists DATA-schema child anchors directly (getAnchorsBySchemaAndAddressList) while the ADR-0048 `system` exclude reaches a FILE item's tagged DATA only via getActivePinTarget, an interrupted/devnet seed that landed the anchor but not the PIN would leave a listable README that the default `system` exclude cannot hide until a successful reseed. Reorder so the anchor is created immediately before the placement PIN, after the DATA is built and system-tagged. The anchor->PIN gap is the one irreducible window (the PIN's `definition` is the anchor UID, which EAS can't precompute for an atomic batch); shrinking it from ~5 txs to 1 mirrors the client's anchor-last ordering in uploadOnchainFile. An interrupted seed is still repaired on re-run (findAnchor resumes the orphaned anchor into its PIN). All data deps preserved; idempotent. Verified: yarn fork + yarn deploy (seed completes; log shows DATA system-tag BEFORE anchor BEFORE pin for both folder and address READMEs), deployedContracts.ts pin clean, hardhat lint 0. Permanence-tier: Ephemeral Refs: ADR-0048, ADR-0033 Suggested-by: Codex GPT-5 <noreply@openai.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
JamesCarnley
left a comment
There was a problem hiding this comment.
[codex-gpt-5 · merge-readiness heartbeat]
Same-account advisory review: READY TO MERGE
I verified current head 46055f2f696d77a3ac2488b2e8491ecc0f51f8d0 after the final seed-order fix. Native review intake is clear: 0 unresolved threads / 0 needing dev reply. GitHub checks are green and mergeStateStatus is CLEAN.
Local verification on 46055f2f696d77a3ac2488b2e8491ecc0f51f8d0:
yarn install --immutable(exit 0; existing peer warnings only)git diff --check origin/main...HEADyarn next:check-typesyarn next:lintyarn hardhat:lint --max-warnings=0yarn docs:checkyarn workspace @se-2/nextjs test(78 passing)yarn hardhat:test(389 passing)
Submitting as a COMMENT review because all agents are operating through James's GitHub account. No blocking issues remain from my pass.
Summary
EFSFileView.getDirectoryPageFiltered(parent, anchorSchema, attesters[], excludeTagDefs[], minWeights[], cursor, maxItems)skips any item a lens has tagged with ANYexcludeTagDefs[k]atweight >= minWeights[k](union across tags × lenses; capMAX_EXCLUDE_TAGS_PER_QUERY = 8). Backed byEdgeResolver.getActiveTagWeight(O(1) read over the existing TAG index; no kernel state added — weight-neutrality per ADR-0041 §4 preserved).README.mdis present. On-chain markdown editor (SSTORE2), sanitized render (react-markdown + rehype-sanitize, no raw HTML), sandboxed HTML preview iframe, content-sniffed bytes.getDirectoryPageFiltered; the previously-unfilteredgetDirectoryPagelisting fallback has been removed, so every directory read is lens-scoped + filtered. README issystem-tagged → hidden by default in the listing, yet still renders in the Overview pane (resolved by exact router path, independent of the directory filter).Why
The descriptive-label filter (ADR-0042) was deliberately client-only, which is useless for on-chain consumers ("give me this folder with nsfw excluded" required an expensive second loop). ADR-0048 extends the same
weight >= thresholdprojection into the view layer with a caller-supplied threshold, so a contract/client gets a filtered page in one call. The explorer then had to actually use it — and the old unfilteredgetDirectoryPagefallback was both dead code (the lens list is always non-empty, so it never ran) and the one read path that could leaksystem/nsfw. Removing it makes an empty lens list fail safe (empty grid, never unfiltered content).Permanence tier
Mixed. Durable:
EFSFileView/EdgeResolver(pre-mainnet contract surface, becomes Etched at launch) — ADR written before code, ABI-additive only, deterministic pin unchanged in address/schema-UID. Ephemeral: all ofpackages/nextjs/(debug UI). No Etched surface touched.Specs / ADRs touched or checked
specs/02,specs/03,specs/overview.md— updated to the multi-tag signature.docs/adr/README.md— indexed ADR-0048 with the ADR-0042 forward-pointer.Test plan
yarn hardhat:test— 387 passing, incl. multi-tag union, per-tag thresholds, length-mismatch + cap reverts, cross-lens exclusion, revoked-tag/swap-and-pop regression, phase-1 budget trip, file vs folder target asymmetry.yarn next:check-types+yarn next:lint— clean./docshidesREADME.md(no flash, not stuck); Overview renders it; togglingsystemoff reveals it and back on re-hides it; folder navigation loads; round-trips verified after each change.Known follow-ups (documented in docs/FUTURE_WORK.md, not blocking)
system/nsfwfolders are hidden in the grid but still appear in the left nav tree (pre-existing; the tree was never filtered). AGENT-NOTE at the read site + FUTURE_WORK entry; fix = thread the resolved exclude defs through TopicTree.utils/only) — FUTURE_WORK entry with the extract-to-helper plan + missing contract entry-guard/address-target cases.Agents involved
Supersedes #25 (Overview pane) and #26 (filter contract) — both are folded into this single PR. Recommend squash-merge (collapses intermediate commits + their trailers into one clean commit).
🤖 Generated with Claude Code