The privacy baseline is close, but the current deployment still logs client IPs and paths through Uvicorn, so the headline "no logs" claim is false despite nginx access_log off and a tight self-only CSP (templates/base.html:62, templates/stake.html:11, templates/why.html:6, /etc/nginx/sites-available/ctaz-explorer:11-13, /etc/systemd/system/ctaz-explorer.service:11,14-15, /root/.zeven/watchers/ctaz-explorer.log:3625-3635). The biggest correctness gap is shieldedvault: the site promises vault matching, badges, and cards, but build_verification() hardcodes vault: None and nothing ever calls lookup_vault() (main.py:90-95, main.py:598-633, templates/verify.html:5,35,39, templates/vaults.html:23, templates/why.html:21). Human-facing error paths also fail ungracefully because HTML 404/503 routes collapse to raw JSON (main.py:190-197, main.py:263-272, main.py:463-472, /root/.zeven/watchers/ctaz-explorer.log:3684-3689). The deployment implicitly depends on 127.0.0.1:8232 for /z because ZCASH_MAINNET_RPC_URL is not set or asserted, although the current live app does appear to hit a distinct mainnet node because the mainnet anchor sample resolves at height 3286631 (/etc/systemd/system/ctaz-explorer.service:7-11, main.py:15,17-37,684-729,802-812, data/zap1-anchors-zcash.json:3-9, /root/.zeven/watchers/ctaz-explorer.log:3626-3629). Direct curl to the public hostname and local RPC sockets was blocked from this sandbox, so live observations here rely on the checked-in nginx/systemd config and the current request log where direct socket probes were impossible.
- [Privacy claim breach, critical,
templates/base.html:62;templates/stake.html:11;templates/why.html:6;/etc/systemd/system/ctaz-explorer.service:11,14-15;/root/.zeven/watchers/ctaz-explorer.log:3625-3635, suggested fix: run Uvicorn with--no-access-log, keep nginxaccess_log off, and log only exceptions or coarse health summaries.] - [Shieldedvault verification is advertised but not implemented, high,
main.py:90-95;main.py:598-633;templates/verify.html:5,35,39;templates/vaults.html:23;templates/why.html:21;static/style.css:481-509, suggested fix: either implement tx-to-vault matching end to end and render the badge/card, or mark vault matching as a stub everywhere until a tx-addressable schema exists.] - [HTML error paths fail ungracefully, high,
main.py:190-197;main.py:263-272;main.py:463-472;/root/.zeven/watchers/ctaz-explorer.log:3684-3689, suggested fix: add404.htmland503.html, register handlers forstarlette.exceptions.HTTPExceptionplus generic 500/503 cases, render templates for non-/apiroutes, and keep JSON only under/api/*.] - [Mainnet routing is implicit and a chain mismatch would be silently mislabeled, high,
main.py:15,17-37,684-729,802-812;/etc/systemd/system/ctaz-explorer.service:7-11;data/zap1-anchors-zcash.json:3-9;/root/.zeven/watchers/ctaz-explorer.log:3626-3629, suggested fix: setZCASH_MAINNET_RPC_URLexplicitly in the unit and assert expected chain ids on startup before serving/or/z.] - [Community-ownership claims are under-backed, medium,
README.md:3,17-24,37-39;templates/base.html:62;templates/why.html:6;templates/stake.html:31, suggested fix: add a realLICENSEfile, update clone-and-run docs for18232plus mainnet envs, and stop claiming MIT or stale LOC counts unless the repo state matches.]
- [Disable app access logging and keep only error logs, impact 5, effort 1, change
/etc/systemd/system/ctaz-explorer.servicesoExecStartincludes--no-access-log; keep nginxaccess_log off; send exceptions to stderr or structured error logging only.] - [Add chain identity assertions and explicit envs, impact 5, effort 2, on startup call
getblockchaininfofor both RPC clients, requirectaz-s1for/andzcash-mainnetfor/z, and refuse to serve mismatched nodes rather than hardcoding labels.] - [Fix shieldedvault truthfulness, impact 5, effort 2, either wire vault matching into
build_verification()and tx rendering or rewriteverify/vaults/whycopy to call vault support a stub until the schema is real.] - [Cache pool history, impact 4, effort 2, memoize
fetch_pool_history(pool_id, tip)for about 30 seconds and share it between/pool/*and/api/pool/*; if tip has not advanced, reuse the cached series.] - [Replace raw JSON errors with themed HTML errors, impact 4, effort 2, add
404.htmland503.html; branch onrequest.url.path.startswith('/api/')inside the handlers; reuse the existing validation text from/verifyfor malformed txids and heights.] - [Ship the discoverability bundle, impact 4, effort 2, add description/canonical/OG/Twitter/robots meta to
base.html, a small self-hosted preview image,robots.txt,sitemap.xml, and a physical/favicon.ico.] - [Fix Zcash home anchor labels, impact 3, effort 1, apply the same
event_labelaugmentation used inzcash_anchors()before renderinganchors_previewon/z.] - [Sync README and operator prose to current behavior, impact 4, effort 1, document
ZEBRAD_RPC_URL=18232andZCASH_MAINNET_RPC_URL, remove stale LOC claims, and link exact repo/commit targets for protocol claims.] - [Tighten search ergonomics, impact 3, effort 1, add
maxlengthplusspellcheck='false'to the global search input and stop treating anyt1*/u1*/zs*prefix as valid enough to redirect without format validation.]
- No literal dead internal
hreforactiontargets were found in template source: every static target maps to a FastAPI route or the static mount, and the sample mainnet anchor links in the registry resolve live at/z/block/3286631and/z/tx/98e1...(templates/base.html:8,14-21,25,35-52;templates/home.html:37-49;main.py:263-903;data/zap1-anchors-zcash.json:3-9;/root/.zeven/watchers/ctaz-explorer.log:3626-3629). - The copy overstates mainnet parity: home says "the same surfaces run in production on zcash mainnet," but only
/z,/z/block,/z/tx, and/z/anchorsexist; there are no/z/vaultsor/z/eventsroutes (templates/home.html:35-54;main.py:684-825). - README and
/whyboth understate code size (~200 loc,500 lines of python) versus the current repo, wheremain.pyalready runs to line 904 (README.md:3;templates/why.html:10;main.py:904). - The home banner and
/whypage drift into self-congratulation and competitor framing: "explorer built in a day" and "no other explorer... because nobody has written it yet" should be replaced with receipts or cut (templates/home.html:15-16;templates/why.html:20). - The footer label
/api/verifyis misleading because itshrefis a hard-coded all-zero txid that is guaranteed to 404, not a neutral API landing page (templates/base.html:52;main.py:656-663). - Several operational claims need sources or links instead of bare prose: the speculative commit
d880b65,wallet/src/lib.rs:1923, and the staking-window numbers are asserted but not linked to an exact blob or repo path (templates/params.html:25,30-35;templates/why.html:14,23). - External link liveness could not be rechecked from this sandbox because outbound DNS/socket access to third-party hosts was blocked; the target inventory is in
templates/base.html:56-62,templates/anchors.html:5,templates/params.html:23,30,templates/vaults.html:5, andtemplates/why.html:32,50.
- Pool pages are slow by design: each request walks a 200-block window and does
getblockhashplusgetblockper height, so one page or API hit drives about 400 RPC round trips before rendering (main.py:50;main.py:410-440;main.py:463-515). - The pool history work is duplicated between HTML and JSON surfaces with no shared cache, so
/pool/orchardand/api/pool/orchardboth pay the full cold cost (main.py:463-497;main.py:500-515). - Home already uses
asyncio.gather, but each of the 10 recent blocks still paysgetblockhashthengetblockplusget_tfl_block_finality_from_hash; that is a small but real latency budget that could be cached or walked backward from tip hashes (main.py:200-216;main.py:265-276). - Static assets are proxied through the generic nginx location to Starlette
StaticFileswith no dedicated immutable cache rule for/static/, so frontend cache wins are being left on the table (main.py:13;/etc/nginx/sites-available/ctaz-explorer:16-24). - nginx has
gzip on, butgzip_typesis commented out and there is no brotli config, so compression is likely limited to default HTML types rather than CSS and JSON too (/etc/nginx/nginx.conf:52-59).
base.htmlonly emits charset, viewport, title, icon, and stylesheet; there is no description, canonical, robots, Open Graph, Twitter card, or apple-touch-icon anywhere (templates/base.html:4-8).- Crawlers are already asking for
robots.txtand getting 404s, so discoverability debt is not theoretical (/root/.zeven/watchers/ctaz-explorer.log:3633,/root/.zeven/watchers/ctaz-explorer.log:3396-3403,/root/.zeven/watchers/ctaz-explorer.log:3556,/root/.zeven/watchers/ctaz-explorer.log:3604). - The favicon is data-URI only, which leaves
/favicon.ico404 and even produced a crawler request for the percent-encoded data URI path; ship a real static icon (templates/base.html:7;/root/.zeven/watchers/ctaz-explorer.log:3653-3654,/root/.zeven/watchers/ctaz-explorer.log:3698). - Entity titles are decent, but several high-value pages still have generic titles (
verify,support,finalizers,protocol parameters,{{ pool }} pool) without chain or site context, which weakens tabs and search snippets (templates/verify.html:1-4;templates/stake.html:1-4;templates/finalizers.html:1-4;templates/params.html:1-4;templates/pool.html:1-5). - There is no route or asset for
sitemap.xmlin the current route set, so search engines only discover pages by crawling links or prior logs (main.py:263-903).
- Semantic landmarks are already present with
header,nav,main, andfooter, which is the right baseline (templates/base.html:11-31). - The global search input has no visible label or
aria-label, so the control depends entirely on placeholder text; the verify form has better hash-oriented ergonomics than the global search box (templates/base.html:25-27;templates/verify.html:7-9). - The sparkline is already accessible enough to start because the SVG gets an
aria-label; keep that when refactoring performance (main.py:443-459). - Generic link color contrast is weak:
#cf9b22on#faf8f3is about2.37:1, and links are mostly un-underlined outside the banner, so color alone is doing too much work (static/style.css:1-13;static/style.css:187-192;static/style.css:248-253). - Only the search input gets explicit focus styling; card links and nav links rely on default browser focus, with no
:focus-visibletreatment for keyboard users (static/style.css:89-93;static/style.css:451-458).
- The stats and footer grids should collapse reasonably on narrow screens because they use
auto-fitwith 140px and 150px minimums (static/style.css:107-115;static/style.css:550-557). - The header nav has no wrap strategy and uses a single-line flex row with seven links, so narrow screens are likely to crowd or wrap awkwardly before content starts (
templates/base.html:11-22;static/style.css:31-54). - Tables have no overflow container, so recent-block and pool tables will squeeze rather than scroll cleanly on 360px widths (
static/style.css:154-179). - Base text size is a sane 15px, so legibility is probably acceptable; the weak point is navigation density, not body copy (
static/style.css:17-30). - Tap targets are mixed: search/button and stat cards are large, but nav links remain small inline text targets (
static/style.css:47-53;static/style.css:72-105;static/style.css:116-123).
-
- Make Orchard dominant on the home page and add a shielded/transparent ratio card. The data is already on the home handler; the change is mostly layout and emphasis (
main.py:278-297;templates/home.html:4-12;static/style.css:107-128).
- Make Orchard dominant on the home page and add a shielded/transparent ratio card. The data is already on the home handler; the change is mostly layout and emphasis (
-
- Add a shielded-density stat for the last N blocks by reusing
tx_value_flow()across the recent block window and counting blocks withis_shieldedtrue (main.py:239-260;main.py:265-276).
- Add a shielded-density stat for the last N blocks by reusing
-
- Add a recent shielded-activity feed that surfaces the last block with Orchard actions or Sapling activity, not just the last block chronologically (
main.py:239-260;main.py:305-336;templates/home.html:19-33).
- Add a recent shielded-activity feed that surfaces the last block with Orchard actions or Sapling activity, not just the last block chronologically (
-
- Replace the numeric finality gap with a tiny two-line SVG showing tip versus finalized height; the inputs are already present on the home page today (
main.py:281-295;templates/home.html:5-7).
- Replace the numeric finality gap with a tiny two-line SVG showing tip versus finalized height; the inputs are already present on the home page today (
-
- Add a
/comparepage that shows cTAZ and Zcash mainnet side by side. The dual-chain plumbing already exists inCHAINS,rpc_zcash, and the/zhandlers, so this is mostly composition work (main.py:15;main.py:17-39;main.py:684-825).
- Add a