Skip to content

feat: on-chain multi-tag directory filter (ADR-0048) + Overview pane, with explorer consuming it#27

Open
JamesCarnley wants to merge 95 commits into
mainfrom
onchain-filter-integration
Open

feat: on-chain multi-tag directory filter (ADR-0048) + Overview pane, with explorer consuming it#27
JamesCarnley wants to merge 95 commits into
mainfrom
onchain-filter-integration

Conversation

@JamesCarnley

Copy link
Copy Markdown
Member

Summary

  • On-chain, multi-tag directory exclusion filter (ADR-0048). EFSFileView.getDirectoryPageFiltered(parent, anchorSchema, attesters[], excludeTagDefs[], minWeights[], cursor, maxItems) skips any item a lens has tagged with ANY excludeTagDefs[k] at weight >= minWeights[k] (union across tags × lenses; cap MAX_EXCLUDE_TAGS_PER_QUERY = 8). Backed by EdgeResolver.getActiveTagWeight (O(1) read over the existing TAG index; no kernel state added — weight-neutrality per ADR-0041 §4 preserved).
  • Per-item Overview markdown pane. Minimized by default; auto-expands when a README.md is present. On-chain markdown editor (SSTORE2), sanitized render (react-markdown + rehype-sanitize, no raw HTML), sandboxed HTML preview iframe, content-sniffed bytes.
  • Explorer consumes the filter, and ONLY the filter. The directory grid is wired to getDirectoryPageFiltered; the previously-unfiltered getDirectoryPage listing fallback has been removed, so every directory read is lens-scoped + filtered. README is system-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 >= threshold projection 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 unfiltered getDirectoryPage fallback was both dead code (the lens list is always non-empty, so it never ran) and the one read path that could leak system/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 of packages/nextjs/ (debug UI). No Etched surface touched.

Specs / ADRs touched or checked

  • ADR-0048 (new) — view-layer tag-exclusion directory filter; extends ADR-0042 (confirmed byte-for-byte unchanged) and preserves ADR-0041 §4 kernel weight-neutrality.
  • 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.
  • Manual (browser): default /docs hides README.md (no flash, not stuck); Overview renders it; toggling system off reveals it and back on re-hides it; folder navigation loads; round-trips verified after each change.
  • Review: an 8-lens multi-agent pass (Solidity, frontend, adversarial-leak, Overview security, governance, tests, hook hygiene, simplifier) with adversarial per-finding verification → 0 blockers; the 3 should-fix (a Loading-wedge edge case + two stale safety notes) are fixed in this branch.

Known follow-ups (documented in docs/FUTURE_WORK.md, not blocking)

  • TopicTree sidebar is not exclude-filteredsystem/nsfw folders 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.
  • Frontend exclude-filter fail-safes lack unit coverage (runner globs utils/ only) — FUTURE_WORK entry with the extract-to-helper plan + missing contract entry-guard/address-target cases.

Agents involved

  • Dev: Claude Opus 4.8
  • Review: Claude Opus 4.8 (8-lens workflow + adversarial verification; earlier 4-lens integration review)
  • Orchestrator: human (@JamesCarnley)

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

JamesCarnley and others added 30 commits June 10, 2026 18:16
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>
JamesCarnley and others added 3 commits June 13, 2026 23:16
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 JamesCarnley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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/nsfw hides
  • 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.

Comment thread scripts/pr-review-intake.sh Outdated
Comment thread packages/nextjs/components/explorer/OverviewEditorModal.tsx Outdated
Comment thread packages/nextjs/app/explorer/[[...path]]/ExplorerClient.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/app/explorer/[[...path]]/ExplorerClient.tsx Outdated
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/lib/efs/uploadOnchainFile.ts Outdated
Comment thread packages/hardhat/scripts/seed-impl.ts Outdated
…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 JamesCarnley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread packages/hardhat/deploy/10_seed_demo_tree.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/lib/efs/applySystemTag.ts
JamesCarnley and others added 2 commits June 14, 2026 00:15
…/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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/components/explorer/FileBrowser.tsx
JamesCarnley and others added 2 commits June 14, 2026 00:23
…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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/components/explorer/FileBrowser.tsx
Comment thread packages/nextjs/hooks/efs/useItemOverview.ts
…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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/components/explorer/FileBrowser.tsx

@JamesCarnley JamesCarnley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread packages/nextjs/docs/overview-pane-design.md
JamesCarnley and others added 2 commits June 14, 2026 00:58
…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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/app/explorer/[[...path]]/ExplorerClient.tsx
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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/nextjs/lib/efs/uploadOnchainFile.ts Outdated

@JamesCarnley JamesCarnley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Comment thread packages/nextjs/components/explorer/FileBrowser.tsx
JamesCarnley and others added 2 commits June 14, 2026 11:24
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/hardhat/scripts/seed-impl.ts
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 JamesCarnley left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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...HEAD
  • yarn next:check-types
  • yarn next:lint
  • yarn hardhat:lint --max-warnings=0
  • yarn docs:check
  • yarn 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant