* chore: ignore the impeccable hook session cache
hook.cache.json records per-session edit counts keyed by absolute paths on the
machine that produced it, so it is local state rather than project config.
config.json, which carries the shared detector settings, stays tracked.
* chore(deps): add the redesign's UI dependencies and fix module resolution
Adds @radix-ui/react-select (the sort menus), tw-animate-css (the enter/exit
utilities Radix drives from data-[state]), github-slugger (heading anchors for
docs search), motion, gsap, cobe, and the Magic UI / Aceternity runtime deps.
tsconfig gains baseUrl and the "@/*" path mapping. Without it the shadcn CLI
resolved "@/lib/utils" as a literal directory and wrote vendored components
into a folder named "@" at the repo root.
eslint.config.js exempts the vendored registry components from
react-hooks/purity and react-hooks/set-state-in-effect. Those files are
generated and `shadcn add` overwrites them, so the violations cannot be fixed
in place.
* feat(ui): vendor the Magic UI and Aceternity components
Installed through the shadcn CLI from the Magic UI and Aceternity registries.
They are checked in rather than depended on, which is how those registries are
meant to be used: the source is the deliverable.
Two fixes applied on top of what the registry shipped, both silent failures:
- The pulse keyframes declared `boxShadow:` — a JS property name in a CSS
block. Browsers dropped the declaration, so PulsatingButton's glow did
nothing. Four declarations corrected to `box-shadow`.
- ScrollProgress hardcoded a purple/pink/orange gradient. This palette is
monochrome and inverts with the theme, so the bar now paints the foreground.
* feat(theme): rebuild the token system on gitlawb.com's identity
Colours are extracted from the live gitlawb.com rather than approximated: pure
#000 ground, pure #fff text, Geist Mono throughout. The over-scoped theme
customizer is removed in favour of a single dark/light toggle.
Notable fixes folded in here, each of which broke something silently:
- The `*` border-color reset sat outside any cascade layer, so it beat every
layered border-color utility regardless of specificity. `border-success`,
`border-danger`, and `border-transparent` all painted the same faint white.
Moved into @layer base.
- Syntax-highlight .tok-* colours existed only for dark, leaving near-white
glyphs on the light ground. Both sets now defined.
- The light palette had been mirrored from dark, but alpha compositing is not
symmetric: black at 45% over white is 1.75:1 where white at 45% over black
is 10:1. The light ramp is retuned, and --success moved to #00712f, which
clears 4.5:1 on both the page and its own tint.
- AuroraText and AnimatedGradientText painted through background-clip with a
hardcoded ramp, so they rendered invisible in light mode with nothing for a
contrast check to see. The ramp is now themed.
Radius: rules and list hairlines stay square, controls take a full round to
match the search field, and floating panels take 12px.
useIsDark subscribes to the class on <html> via useSyncExternalStore, because
canvas components (the globe, the icon cloud) bake colours into textures and
image URLs and must re-render on a theme flip. useTheme cannot serve that — it
holds its own state per call site, so a second caller never hears the toggle.
useTheme also forces a full style recalculation after the flip. Chromium does
not reliably invalidate `color` on existing elements when an inherited custom
property changes on the root: measured after one toggle, 76 of 236 .text-muted
elements kept the previous theme's colour while a freshly created element with
the same class in the same parent resolved correctly.
* feat(ui): frameless control kit, and drop the native select
At zero radius a bordered control reads as a hard box, and a listing page puts
hundreds of them on screen at once. Buttons are frameless by default, fields
are ruled with a single underline, and a frame or a fill is reserved for the
control that has to stand apart. SearchField defines the one search shape every
listing page uses, so there are no longer four slightly different ones.
Dropdown replaces the native <select>. A native select paints its option list
as an operating-system popup that lives outside the document: it cannot take
this page's colours, its radius, or its type, and on Windows it draws as a
grey box. Styling the closed control did nothing to the opened one. Built on
Radix Select, which renders the list in the page while keeping the keyboard
and screen-reader behaviour the native control had.
SegmentedControl replaces loose pill rows for single-choice filters. One track,
one indicator that slides between options, positioned from the active button's
measured box and animated with a CSS transition. Motion's layoutId is the
idiomatic way to write this, but the measured version's correctness is
checkable without watching an animation, and it does not depend on Motion's
projection surviving the router transition a filter click schedules.
Also fixes every `rounded-[--radius]` in the kit. That is Tailwind v3 syntax
for a CSS variable; v4 requires `rounded-[var(--radius)]`. The class compiled
to nothing, so these controls had been drawing square no matter what the token
said.
* feat(layout): rebuild the top bar, footer, and overview
The nav rail is a single Motion element shared across links via layoutId, so
it slides between items rather than cross-fading. The palette trigger is
icon-only with the shortcut in its tooltip.
Adds lib/platform.ts. The palette handler accepts metaKey || ctrlKey, but the
UI hardcoded the Mac glyph, so Windows and Linux visitors were told to press a
key their keyboard does not have. modifierKeyLabel() reports the right one.
* feat(repos): rebuild the repository index
One toolbar row: the shared search field, a sort dropdown, and the fork filter
as a segmented control. Rows are denser and the constant-value pills that
carried no signal are gone.
The fork filter runs over the repos already fetched rather than the whole node,
so while the list is paginated the scope is stated. Without it, filtering to
"forks" answered "no repositories match" having examined 50 of 3,207 — which
reads as "this node has no forks".
timeAgo clamps at zero. The node's clock can sit ahead of the browser's, which
produced timestamps like "-28s ago" on freshly pushed repos; under five seconds
now reads "just now". Two tests cover it.
* feat(repo-detail): rebuild the repository detail page
Removes StatsPanel, whose figures duplicated the header. The file tree, commit
list, certificates, issues, and pulls move onto the shared control kit.
Fixes a deep-link hang: opening ?tab=certs directly left the tab on "loading…"
forever. The ref marking a tab as started survives StrictMode's simulated
remount, while the cleanup aborts the in-flight fetch — so the retry was
skipped because the marker said the work was already underway. The marker is
now cleared when its request aborts.
* feat(agents): trust sorting, band filters, and the ecosystem cloud
Adds four sorts and a trust-band segmented filter. Band counts are computed
after the search but before the band filter, so each band reports what it would
yield rather than collapsing to the active one.
AgentRow drops the gauge and the tier colours for a text readout, and prints a
status chip only when the status departs from active — at 4,718 agents the
decoration was the majority of the ink.
AgentEcosystem is an icon cloud of the tooling that can drive this network.
Its heading is "Works with" deliberately: the agents API returns did:key
identities with no vendor field, so nothing here claims which of these have
agents registered. Mark colours are part of the CDN request URL, so they are
re-fetched per theme — white logos are invisible on the light ground.
* feat(network): globe, federation map, and peer orbit
The globe is deliberately illustrative. The node API exposes no geography — a
peer record is { did, http_url, last_seen, reachable } and nothing more — so
the markers are a stock world spread, not peer locations. A map pin gets read
as a claim about where a machine is, so the real federation view stays in the
topology diagram and the node cards below, which are driven by reachability.
Its colours are painted into a canvas and cannot come from CSS tokens, so they
switch on the theme here: a dark sphere on the light ground reads as a hole in
the page. The component replaces cobe's default config wholesale rather than
merging, so every field has to be supplied — the stock default ships ten
hardcoded demo cities.
* feat(events,tasks): rebuild the feeds
Events moves to the shared SearchField; it was the last page hand-assembling
its own search input, a near-verbatim copy that would have drifted the moment
SearchField changed. The task status filter becomes a segmented control, with
each status keeping its colour dot — SegmentedOption gained an icon slot for
it, and the indicator now tracks both axes so a long filter row can wrap
without the indicator landing on the wrong line.
* feat(docs): rebuild the documentation page
Three columns: a sticky section nav, the article, and the scroll-spy TOC. The
old layout capped the prose at 70ch and left the right half of the screen
empty. Every document opened with its title twice — once from the page header
and once from the markdown h1 — so the leading h1 is dropped.
Each fence gains a language label and a copy button. Section headings gain
anchors. A prev/next pager closes each page, since the four documents are meant
to be read in order.
"Read this as text" is now on the page at every width. It was hidden below the
lg breakpoint, so the single thing that distinguishes these docs — that an
agent can fetch the same words as raw markdown — was invisible on anything
narrower than a laptop.
Heading ids are now a pure function of the markdown. gfmHeadingId keeps one
slugger on the shared engine and its de-duplication counter carries across
parse calls, and engine.parse awaits Shiki for every fence, so two renders
started close together interleave and feed the same slugger. StrictMode starts
the fetch twice, so this was the normal case: ids came out `prerequisites-1`,
then `-2`. Every deep link and every TOC anchor depended on how many times a
document had been rendered that session. Parses are now serialized, and reset
between.
* feat(docs): search the documentation from the command palette
The four documents are fetched once on first open and split at their headings,
so a result lands on the paragraph that matched rather than the top of a page.
Heading matches outrank body matches; fenced commands are indexed, so
searching "gitlawb/gl" finds the install block. Anchors come from
github-slugger, the same slugger the renderer uses — deriving the rule by hand
would drift and produce links to anchors that do not exist.
The parser splits on either line ending. These files are checked out CRLF, and
splitting on "\n" alone leaves a trailing "\r" that the heading pattern can
never match, because `.` in JavaScript excludes carriage return as a line
terminator and `(.*)$` therefore stops short of the end of the string. Every
heading silently failed and each document indexed as one undivided section.
The palette itself takes the radius tokens and a per-kind mark, so a row's
target is legible before the label is read. Its input is now a proper combobox
with aria-controls and aria-activedescendant, and section headings are siblings
of the rows rather than nested inside them — they had been announced as part of
the first result.
* fix(api): never serve the docs hub index in place of a document
vercel.json has no path that 404s: /docs/:slug rewrites into this function and
:slug matches a segment containing a dot, while /(.*) catches everything else
and serves index.html. So when public/docs/<slug>.md missed the filesystem the
subrequest did not fail — it was rewritten back into the same function, which
answered 200 with the hub index because the subrequest's Accept is not html.
That was then served as the requested document, with Content-Type text/markdown
and an hour of CDN caching. response.ok was always true, so the 502 fallback
below it was unreachable.
Two guards. The subrequest carries x-gitlawb-doc-markdown; reaching the handler
with that header means the static file was absent, because a file that exists
is served before rewrites are consulted, so it answers 404 at the first hop.
And the fetched body is rejected if it is the SPA shell or contains the hub's
marker line — fetchIndexHtml already guards its own fetch this way, by testing
the body rather than trusting the status.
A listed slug whose file is missing is a broken deploy, not a passing outage,
so it now returns an uncached 404 rather than a cached 502 claiming to be
temporary.
The test stub modelled the deployment wrongly, which is why this survived: it
returned 404 for absent files, under a comment claiming to reproduce
deployment behaviour. It now performs the actual rewrite. Verified by
disabling both guards — exactly the four new tests fail, the eleven existing
ones pass.
* docs: correct three claims contradicted by this repo's own code
quickstart.md and protocol.md both told the reader to verify a push on the
global /events feed. That feed carries only ref updates gossiped in from peer
nodes; a push this node received directly is recorded as a per-repo signed
certificate and is absent from the feed's data entirely. This is the failure
commit 8e941ee removed from the UI, still present in the prose — and in
quickstart it lands at the worst moment, immediately after a first-time
reader's first push, where an empty feed reads as a failed write. Both now
point at the repository's certificates, which is what agents.md already said.
agents.md listed "clone fails on https:// URL | wrong scheme | clone URLs must
be gitlawb://". The node serves standard git smart-HTTP at the https clone_url,
and protocol.md documents that transport. An agent hitting the most common
documented failure — git-remote-gitlawb missing from PATH — was being steered
away from the one fallback that works.
* docs: record the product truth and the design-detector exemption
PRODUCT.md carries what the code cannot: who the surface is for (developers
and agents evaluating the network), the visual register, the directions that
were tried and rejected, and that Aceternity, Magic UI and GSAP are to be used
as real components rather than reimplemented by hand.
The detector exemption records why "geist mono" is not an overused-font finding
here: the owner requires the explorer to match gitlawb.com, whose live site
computes Geist Mono on body and h1.
* feat(home): close out the overview instead of letting it run out
The page ended wherever the activity list happened to stop. Measured: the
sidebar's content came to 936px against the main column's 532px, and because
both grid columns stretch to the taller one, that left 387px of dead space
under the wide column — the page read as truncated rather than finished.
Three additions, and one of them is placed to fix that directly.
"Repositories on this node" goes in the main column, not below the grid, where
it would have left the hole untouched. Six cards two-up come to roughly the
height that was missing, so the columns now balance at 1061 against 936 and
the remainder falls under the narrow column where it barely reads. The page
had been closing on ref-update lines, which say the network is moving but not
what is on it.
Every field on a card is read from the repository record as the node returns
it: star_count and forked_from are real columns, so the star count and the
fork mark are facts, and both are omitted when they carry nothing. Order is
whatever the node returns — fetchRepos omits the sort param when the sort is
'updated', so nothing claims these are the most recently touched, and the hook
now records that so no future heading asserts it.
"Built for agents" states the machine-readable surface on the front page:
llms.txt, skill.md, and docs/agents.md, each a real file served from this
origin with a copy button handing over the absolute URL an agent needs. This
is the claim the product is built on, and it was only reachable through /docs
— the overview sold the explorer as a dashboard and said nothing about the
part that is unusual.
"Start here" is the closing beat: publish, operate, understand. Every
destination is a route declared in App.tsx.
CopyButton gains srLabel. Its `label` was both the visible text and the
accessible name, so a descriptive name rendered in full as button text. Three
copy buttons in one list now announce which file each belongs to instead of
"copy Copy" three times over, and no existing call site changes visually.
* fix(home): say that the overview's event feed is gossip-only
Both sections read /events/ref-updates, which carries only ref updates
gossiped in from peer nodes. A push this node received directly is recorded as
a per-repo signed certificate and never enters that feed.
Measured against a live node: all 50 events returned had from_peer set, the
newest was 2,919 minutes old, and all six repositories rendered further down
the same page had been updated within the previous 51 minutes. Headed "Latest
ref-update" and "Recent activity" with no qualifier, an actively used node
reads as one that stopped two days ago.
The data was right; the labels were not. They now say what the feed is, in the
same terms /events already uses, and the list states where local pushes are
recorded instead. The ordering and de-duplication were checked at the same
time and are correct: the API returns newest-first, and the first distinct
event is the one printed above the list, so slicing from index 1 skips exactly
the row already shown.
* fix(home): stop the gossip diagram asserting links that do not exist
The P2P section drew animated beams from this node to three named peers taken
from `peers.filter(p => p.reachable)` — HTTP reachability from GET /peers,
which says nothing about libp2p connectivity. Two lines below, the same
section printed the real figures. Measured on a live node: 16 of 75 peers
answer over HTTP, connected_peers 0, gossipsub_mesh_peers 0. The picture
claimed a live mesh while the numbers in the same box denied it.
Beams are now drawn from connected_peers, so none appear when nothing is
connected. They are unlabelled, because /p2p/info reports how many peers are
connected and not which — naming them means borrowing identities from the HTTP
list and asserting a link the API never made. An empty mesh is stated as the
ordinary condition it is, and the HTTP-reachable count is kept as its own
separate figure rather than being quietly used to stand in for gossip.
Clone a repository now names what it clones. The command is real, but it wraps
a ~60-character did:key remote over three lines, so the repository was
unreadable, and the button underneath offered to copy a command for something
the reader could not identify.
Agent tasks gains a proportion bar. This register is overwhelmingly one status
— 198 of 200 completed here — and four figures in a row read as four
comparable quantities. Widths come from the same counts as the numbers, so the
two cannot disagree.
The bar's fills come from a new taskStatusFill rather than from
taskStatusColor with the prefix swapped at runtime. Tailwind generates CSS
only for class names it can read in the source, so "text-success".replace(...)
would have produced no rule and painted an invisible bar.
* fix(repo-detail): tighten the file list to scanning density
Rows carried 24px of vertical padding and came out 47px tall, so a twelve-entry
tree ran past a screen and reading a repository's contents meant scrolling.
A file list is for scanning; the row is now sized to its content at 33px, close
to what a forge uses. On a 168-entry tree that takes the list from 7,932px to
5,579px.
Two smaller corrections in the same pass:
The size column was w-20, too narrow for "32.2 KB", so the unit wrapped under
the number and that row alone stood 4px taller than its neighbours. It is now
w-24/w-28 with the value held on one line.
Both cells carry a fixed 20px line box. The size column is set in mono, whose
taller line box would otherwise make every row that carries a size deeper than
the directory rows that carry none. Rows now measure uniformly.
* fix(ui): NumberTicker rendered 0 until its animation ran
The component rendered `startValue` as its DOM text and only ever wrote the
real figure from the spring's change handler, which is gated behind useInView.
Any statistic below the fold therefore displayed "0" for a value that was
already loaded — on the network page, "0 repos" for a node holding 2,120 —
until the reader happened to scroll it into view, and permanently anywhere the
animation could not run.
Zero is not a placeholder. NodeCard prints "—" when a figure is genuinely
missing, so a rendered 0 reads as a measured fact, and it was false.
The span now renders the true value. When the animation runs its first frame
overwrites this within a tick and the count-up is unchanged; when it does not,
the number is simply correct. Verified with animations suppressed: the four
node cards render 3,208 / 4,718 / 51,451 and 2,120 / 190 / 65, matching
/api/v1/stats and /nodes/node2/api/v1/stats exactly, where previously every
figure rendered 0.
Vendored from the Magic UI registry — `shadcn add number-ticker` overwrites it.
* fix(nav): reset scroll when the page changes
A browser scrolls to the top on a real navigation; a single-page app does not,
because the document never changes. Nothing in this app compensated, so
following the "Next" pager at the foot of a document loaded the next one and
left the reader parked at its bottom, looking at the pager again — the content
had changed but the viewport had not moved. Every cross-page link behaved the
same way; the pager only made it obvious, because that control sits at the very
end of the page.
ScrollToTop keys on the pathname and leaves three cases alone:
- Back and forward. POP means the browser is restoring a position the visitor
already had, and overriding that loses their place.
- Anything carrying a hash, which names a heading to land on. The page owning
that anchor scrolls to it once its content has rendered.
- Query-string changes. Search, filters, sorting and paging rewrite the query
while staying on the same page, and resetting there would yank the view to the
top on every keystroke typed into a search field.
Verified: pager 2707 to 0, sidebar link 1200 to 0, back restores 2707, and
typing into the repository search at scroll 600 leaves it at 623 on a page
still 3367 tall.
Two fixes to the docs anchor scroll, which this exposed.
The hash now comes from the router and is a dependency. Reading
window.location.hash and keying only on the slug meant a jump into a document
already open changed nothing the effect watched, so following a second search
result within the same page did nothing at all.
And the scroll no longer waits for requestAnimationFrame. DocArticle rewrites
that markup in a layout effect, and a child layout effect runs before this
parent effect, so the heading already sits at its final offset. The frame only
bought a dependency on the tab being painted: opened in a background tab, the
callback was deferred and the reader arrived at the top.
9.2 KiB
Product
Platform
web
Users
Primary: developers evaluating the gitlawb network. Someone has heard about gitlawb — from a shared repo link, the docs, a mention of "signed git pushes without a blockchain" — and arrives wanting to judge whether it is real and worth their time. They are skeptical by default. They are looking for proof of life: real repositories with real names, pushes that happened minutes ago, signatures they could verify themselves. The job is forming a judgment, not completing a task. Credibility and comprehension at first contact outrank throughput.
Secondary audiences exist and are served, but do not steer design decisions:
- AI agents and CLI clients, which never execute JavaScript and receive raw markdown instead of the SPA shell (see Capabilities). They are a real and deliberate audience, but they consume a text representation, not the interface.
- Node operators and agent developers who use the peers, network, events, and tasks routes for monitoring and traceability. Their needs are legitimate but secondary; when a layout must choose, it chooses the evaluator.
Product Purpose
A live, read-only web explorer for a single gitlawb node. It renders the node's REST API — repositories, code, commits, pulls, issues, push events, signed certificates, registered agents, peers, and delegated tasks — as a browsable surface.
Its real function is evidentiary. Anyone can claim to run a decentralized git network; the explorer is the artifact that demonstrates one is actually running, populated, and signing. Success is a skeptical developer leaving convinced the network is real and understanding what makes it different.
Positioning
gitlawb's mechanism is verifiable provenance without accounts or a blockchain: identity is an Ed25519 DID keypair generated on the user's machine, every push is signed under RFC 9421 HTTP signatures, and every push produces a ref-update certificate anyone can verify independently. Repositories are cloned over a gitlawb:// scheme via the git-remote-gitlawb helper. Public nodes gate registration behind iCaptcha, a proof-of-intelligence challenge, rather than proof-of-work or human-only CAPTCHAs — agents are first-class participants, not adversaries to be filtered out.
A conventional forge cannot truthfully copy this: its authority comes from an account system and a server that vouches for history. gitlawb's comes from signatures anyone can check without trusting the node that served them.
The explorer's job is to make that mechanism visible rather than merely stated. Certificates, pushers, DIDs, and cert IDs are not incidental metadata on this surface — they are the evidence.
Operating Context
- The explorer is a pure client of one node's REST API (
node.gitlawb.com). It holds no database and no server state of its own. - Nodes serve no CORS headers, so every request goes through a same-origin proxy: Vite's dev proxy locally, Vercel rewrites in production. Peer nodes are reached through per-node path prefixes. This list is duplicated in three files and must be kept in sync.
- Data arrives from a live public network. Repository names, descriptions, and owner DIDs are attacker-influenceable strings and must be treated as untrusted in both rendering and routing.
- Much of the live corpus is machine-generated: at time of writing, roughly 5,900 repositories, many named
pixel-655812,astro-505073, described as "automated repo" or "mirrored from peer", all on branchmain, all public. The interface's real content is repetitive by nature. Designs validated against curated sample data will not survive contact with it. - Users pair the explorer with the
glCLI and plaingit; the site's docs andskill.mdare the installation path. - Deployed on Vercel. Repository and docs routes are intercepted by serverless functions that inject per-page social metadata into the built SPA shell and generate per-repository OG cards.
Capabilities and Constraints
Current functionality. Ten routes: overview, repositories index, repository detail, network, peers, events, tasks, task detail, agents, docs. Repository detail carries file tree, code viewer with syntax highlighting and line-range permalinks, README rendering, commits, pulls, issues, push events, and certificates. Keyboard-first affordances: command palette, fuzzy file finder, shortcuts cheatsheet.
Read-only today; write actions are planned. Star, watch, open an issue, and comment on a pull request are expected to land later. Layouts should reserve credible homes for those affordances now rather than treat every row and header as terminal. No auth or account system exists yet, and the design must not imply one before it does.
Technical constraints that bind design:
- Repository listing is server-paginated via the
X-Total-Countheader; the node clampslimitto 200. Ref-update event limit is likewise clamped to 200. - Full-index search and sort require node support for
q=/sort=and are opt-in behindVITE_SERVER_SEARCH. When off, search and sort apply only to the loaded page, and the interface must say so rather than implying global results. - Text clients that do not advertise
text/htmlreceive raw markdown instead of the SPA shell; social and search crawlers are recognized by user agent and receive HTML. Any change to page structure must preserve both representations. - Agents are unpaginated upstream: fetched once per session and paged client-side.
- Blob classification relies on file extension plus NUL-byte sniffing, because the node guesses content type from extension. SVG is deliberately excluded from inline image rendering.
- Sub-fetches degrade independently: a failed corner of a page renders as unavailable rather than failing the whole page. Empty, partial, and unreachable states are ordinary conditions here, not edge cases.
Terminology (product vocabulary, not decoration): DID, cert / ref-update certificate, push, gossip, peer, node, agent, trust tier (newcomer / contributor / trusted / maintainer), task, delegator, capability, UCAN, iCaptcha.
Brand Commitments
- Name: gitlawb. The site is "gitlawb node explorer" at
explorer.gitlawb.com. - Mark: the official gitlawb glyph, already extracted from the platform brand asset and shipped at Logo.tsx and favicon.svg. Binding — carry it forward.
- Visual register: modern product showcase. The explorer is a shop window for the network as much as a tool — it has to look impressive to a developer meeting gitlawb for the first time. Motion, depth, and visual ambition are wanted, delivered through the Magic UI and Aceternity component libraries the user chose. Data density and readability still bind on list-heavy surfaces (repos index, repo detail), but the overview is allowed to sell.
- Superseded (do not revive):
- "The Register" — light safety-paper ground, engraved guilloche from hashes, wax-seal marks, Bodoni display. Rejected as unprofessional for this audience.
- Plain forge convention — a straight GitHub/Sourcegraph rendition. Correct but judged too plain: "we need to make modern which need to showcase". The conventional structure survives on dense list surfaces; the flat, unaccented treatment does not.
- Libraries are load-bearing. Aceternity, Magic UI, and GSAP were chosen by the user and are to be used as real components, not reimplemented by hand.
Evidence on Hand
- A live, populated node at
node.gitlawb.com— real repositories, real signed pushes, real agents and certificates. This is the product's strongest asset and the interface's actual content. - Written documentation in
public/docs/(quickstart, agents, protocol, node), plusllms.txtand a packaged agent skill atpublic/skill.md. Factual and specific; usable as copy source. - Screenshots of the v0.3.8 interface in
reference/, predating the current code. - Absent — must not be fabricated: testimonials, named users or customers, adoption or traffic numbers, benchmarks, funding, pricing, uptime or SLA claims, security audits, and any endorsement. The network is described in its own docs as alpha. No claim of scale or production-readiness beyond what the live API returns is available.
Product Principles
- Evidence over assertion. The interface's persuasive power comes from showing live, checkable facts — a cert ID, a pusher DID, a push two minutes old — not from adjectives about decentralization. Where a claim and a datum compete for space, the datum wins.
- Design for the corpus that exists. Repetitive machine-generated repositories are the typical case, not the degenerate one. Constant-valued fields carry no information and must not consume prominence.
- The skeptic's questions come first. Is this real, is it live, who pushed this, can I verify it, how do I get it locally. A layout that answers those before it decorates is correct.
- Two representations, one truth. Browsers get the interface; agents and CLIs get markdown of the same facts. Neither is a degraded version of the other.
- Degradation is a designed state. Unreachable nodes, partial loads, and empty results are routine on a live P2P network. They deserve real design, not a spinner and an apology.