Skip to content
fuzheadoPublic

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

665 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“Š WikiBento β€” a Composable Wikimedia Dashboard

WikiBento demo dashboard

WikiBento is a drag-and-drop dashboard for Wikimedia β€” and for the adjacent open collections Wikimedia work leans on: the Internet Archive (items and their views, books you can search inside, audio and video, and how a page looked years ago) and maps over OpenStreetMap. The things worth keeping an eye on live in many places β€” pageview and stats APIs, wiki pages, recent changes, Commons, archive.org β€” and WikiBento brings them onto one reactive board you can arrange, point at a subject, and send as a link: article metrics, external link counts, category sizes, file usage, GLAM impact stats, archive items and books, maps, listings and feeds you can click through β€” plus output widgets that speak (πŸ”Š Speaker) or translate (🌐 Translator) what they hold β€” and they chain: a translation carries its language, so a speaker reads a French paragraph in a French voice (ISSUE-97).

Why this exists at all, now that an agent can generate a dashboard from a sentence: docs/WHY-WIKIBENTO.md β€” the argument, and the running ledger of measured API behaviour (timeouts, CORS realities, honest failure states) that a one-shot dashboard does not have. The widgets are the demo; the ledger is the product.

It's a single-page React app built on react-grid-layout (the grid engine behind Grafana and Kibana), β‰ˆ0.84 MB (~248 KB gzipped), hostable as static files on Toolforge or anywhere. Client-driven, with a server side only where a browser cannot go. Every widget fetches its own data in your browser β€” Wikimedia's REST and Action APIs, Commons, Wikidata, Wikistats, Commons Impact Metrics, WDQS, MinT, and the Internet Archive's metadata, IIIF and Wayback APIs β€” and the app carries a small, audited set of server-side routes for the handful of jobs a browser genuinely cannot do: a map service that refuses browser-shaped requests, two sources that send no CORS header, a short-URL expansion, a cap that has to be enforced before 39 MB arrives, and the Ask advisor's LLM relay, where the key and the prompt live. Each route is inventoried, bounded and guarded β€” the table is Proxied services below, the full inventory is docs/PROXIES.md. No accounts, no login, nothing stored about you. npm run build prints exact figures.

Live: wikibento.toolforge.org Β· Example: Alysa Liu Β· Source: github.com/fuzheado/wikibento


Try It (demos)

Start here: ?config=/demos.json β€” an index board linking the whole suite (onboarding β†’ flagships).

demo what it shows
πŸ‘‹ Article switcher the simplest board: one param, two cards
πŸ”€ Biosphere levels a biology diagram whose every label is clickable β€” the words were read out of the picture by OCR, and each opens the article its meaning wants
πŸ‘† Clickable zones a photograph with six clickable zones β€” its own Commons notes, read from the file's wikitext β€” where a click publishes the peak's name and the πŸ“„ Article Excerpt beside it loads that mountain
πŸ–ΌοΈ Image tour three views of one ridge chained by zones β€” click Next view β†’ and the next picture loads with its own zones ready, because each card holds one view and the file travels on the card's zones channel
πŸ“· Steam turbine, labelled a power-plant diagram whose every label is a door β€” the words were read out of the drawing by OCR, and a click loads the article and its pageviews beside it (MIT OpenCourseWare, CC BY-SA 4.0)
✈️ Aircraft lifecycles an MIT chart of weapon-system lifecycles where each bar is a door β€” the bars were found by a pixel scan, not by eye, and a click opens the aircraft's article and its pageviews
🎞️ Commons gallery Commons' curated layer as data β€” a gallery page's own captions and order, and a click on a tile drives the reader beside it
πŸ—‚οΈ Category gallery Images from a Commons category β€” alphabetical, newest, random or largest β€” with the category's total in the subtitle when a card shows only part of it
πŸ”Ž Page picker one validated page-name box with a wiki beside it β€” three cards follow, and none of them names a project
🌐 Translate chain two params + a three-step dataflow (excerpt β†’ translator β†’ πŸ”Š speaker), where the translation's language picks the spoken voice
πŸŽ›οΈ Params & galleries buttons, a slider and a month stepper driving galleries
πŸ”€ Dataflow pipeline Text List β†’ Filter β†’ Count β†’ Display
βš–οΈ A Wikimedian in Residence, four years in a programme measured in full: three residents in four languages, the impact of their Commons uploads from Commons Impact Metrics (12- and 24-month trends, top files, the 108 wikis that use them), one file's 15-month traffic, and the core articles' language coverage from Wikidata
πŸ›οΈ One template, any institution exact GLAM impact stats (Commons Impact Metrics) β€” type any institution/category in one box and every CIM card follows; five flagship collections are the starting shortlist
πŸ”Ž Article vitals summary, traffic, ORES quality, WikiProjects, edits, images
🧠 Query power live SPARQL across WDQS, Humaniki and QLever β€” and a result's coordinates drawn as a map
🌍 Born in 1929 one board, one story: two lives on a shared timeline (21 dated events), plus each subject as prose, images and traffic β€” Anne Frank and Martin Luther King Jr., both born in 1929
πŸ•°οΈ Two lives, one axis lives on a shared timeline, two ways (calendar or aligned at birth), zoomable 8Γ—, per-card title and light/dark card theme β€” Anne Frank Γ— Martin Luther King Jr., Marie Γ— Pierre Curie
πŸ“„ Embed any page frame a 3D model (Objectium) in a card
πŸ“„ Read a document a 329-page PDF and a DjVu read page by page β€” turn, zoom, jump, facing pages β€” beside an Internet Archive book (one reader, two archives), plus a small work transcribed on Wikisource whose page text appears with its proofreading grade (Validated β€” and Not proofread (uncorrected OCR) where that is the truth)
πŸ“° A wiki page in a card any wiki page rendered in a card β€” the lead, one section, or the whole thing β€” with its tables and images, and every blue link a pick target
πŸ–Œ One image filling the box a single Commons file as an edge-to-edge tile β€” letterboxed or cropped, with the chrome switched off
πŸ–ΌοΈ Edge to edge the switch that takes a card's chrome away β€” a QR code, a Commons document, an IA book, a wiki page
πŸ—ΊοΈ A map in a card a coordinate, a Wikidata item or a page title β€” plus any number of points, and the same list as a path or an area; geometry from another widget or pasted GeoJSON
🎞 Story mode one Wikipedia article as a continuous scroll β€” its images and captions, the article's own chapters, and a panel per image chosen by that image's shape
πŸ“– Internet Archive a scanned book you can turn, zoom and search inside (16 and 304 pages) read as facing pages, archive items by media type, and two players streaming the real files β€” an 11-minute film and a LibriVox playlist, straight from archive.org/download/
πŸ“° The front page, as boxes In the news, Did you know, On this day, the day's featured article and picture β€” five Wikipedia templates rendered with the wiki's own HTML and styles, each linked back to its template
πŸ‘† Click through Click a sea in a live Wikipedia box and another widget uses it: the page viewer loads the article and the value card shows the string that travelled. βš™ Links in the box decides whether a click opens a tab, sends to the board, or both
🧩 Full catalog all 36 widget types on one board β€” its article switcher drives five cards

Every board also works in kiosk mode β€” add ?kiosk=1. Configs are plain JSON (see docs/JSON-FORMAT.md); any URL, on-wiki page or GitHub raw file works the same way. Writing a board from outside the app β€” in a chat, a notebook or another tool? Read the served contract instead: https://wikibento.toolforge.org/board-guide.md (generated from the manifest and the two specs above, with the whole catalog and the two reference grammars), and the machine-readable pair https://wikibento.toolforge.org/manifest.json + https://wikibento.toolforge.org/dashboard.schema.json.

A ready-made dashboard hosted on Wikimedia Commons also loads from an on-wiki page β€” ?config=https://commons.wikimedia.org/wiki/Commons:WikiPortraits/Bento-demo.json (or the short link ?config=https://w.wiki/TR9R, expanded via the same-origin /api/resolve endpoint). Any CORS-enabled host works the same way.

Widgets

36 widget types. Each section below is a category in the in-app Add Widget panel; the full table β€” what every widget shows and the API behind it β€” is docs/WIDGET-CATALOG.md.

category widgets
Articles (5) πŸ“Š Article Pageviews Β· πŸ“„ Article Excerpt Β· πŸ•“ Edit History Β· πŸ… Article Quality (ORES) Β· 🧭 WikiProject Assessment
Categories & GLAM (11) πŸ“ Category Size Β· πŸ“ˆ GLAM Category Usage Β· and nine CIM widgets: snapshot, views over time, top files / wikis / pages / editors, global leaderboard, file spotlight, file traffic
Files & Media (5) πŸ–ΌοΈ Gallery (an article β€” images and video β€” a Commons gallery page, a wiki category, or a pasted list) Β· πŸ–ΌοΈ File Usage by Wiki Β· 🌐 360Β° Panorama Viewer Β· 🎬 Video / Media Player Β· πŸ“„ Document Reader
Rankings & Platforms (4) πŸ”— External Link Count Β· 🌐 Wiki Stats Β· πŸ† Top 10 Wikipedias Β· πŸ”₯ Top Wikipedia Articles
Content & Embeds (9) πŸŽ›οΈ Board Controls Β· πŸ“ Text / Markdown Β· πŸ”³ QR Code Β· πŸ”Š Speaker Β· 🌐 Translator (MinT) Β· πŸ“‹ Article List Β· πŸ“„ Wiki Page Β· πŸ—ΊοΈ Map Β· πŸ“° Wikipedia Box
Queries & Power (1) 🧠 SPARQL Query (WDQS · QLever · Humaniki)
Dataflow (4) 🧾 Text List Β· πŸ”Ž Filter Lines Β· πŸ”’ Line Count Β· πŸ–¨οΈ Value Display
Web & History (3) πŸ“¦ IA Item Stats Β· πŸ“– IA Book Β· πŸ•°οΈ Wayback Snapshot Gallery (alpha)

All 27 data-driven widget types render live data in the browser; the 9 static ones (Text/Markdown, QR Code, Board Controls, Speaker, Wiki Page, Text List, Filter Lines, Line Count, Value Display) render from config β€” no fetch.

Features

The board

  • Drag, drop, resize β€” a 12-column grid with vertical compaction; grab a title bar to move a widget, the bottom-right corner to resize it
  • Responsive β€” under 768px the grid collapses to a single-column card stack in grid reading order, so a desktop arrangement still makes sense on a phone
  • Presentation β€” β›Ά Present hides every editing affordance and locks the grid for a clean data wall; ?kiosk=1 loads any board that way directly (a shareable presentation link), and β–£ Lean does the same without taking over the screen
  • Story mode β€” any gallery can present an article's images as one continuous scroll: each image gets the panel its own shape deserves (an edge-to-edge panorama, a centred plate for tall portraits at native size, or an image-and-text split whose text side alternates), with the article's own sections as chapters and a progress bar down the side. Set Display β†’ Story on any gallery and point it at an article, or open ?config=/story-demo.json.json` (Gallery, Images come from An article). The technique is ported from the Met / Google-Arts-&-Culture prototype; in Present or Lean mode a single tall card is the whole screen, which is the way to show it to a room.
  • A map in a card β€” πŸ—ΊοΈ Map takes a place: a coordinate (48.8584, 2.2945), a Wikidata item (Q64), or a page title (read from that wiki first, following redirects, then from its Wikidata item). Wikimedia's own map service draws it at the card's own size, with our pin at the centre, OpenStreetMap's credit on the image, and zoom, fit and map label language under βš™. One image means no tiles and no map library β€” so it prints and exports properly, and it works Edge to edge. Points take the same vocabulary, one per line (up to 100), drawn as markers on the image; with Frame the points on, the centre and the zoom are computed to fit them all. The same list can draw a shape β€” A path (a line through the places, in order) or An area (a polygon; the ring is closed for you) β€” and geometry also arrives as pasted GeoJSON (RFC 7946, the format every map and drawing library speaks: rings closed, swapped axes detected, sizes capped, and a shape that cannot be read says why) or from another widget: a map card publishes what it draws as GeoJSON, so one card can draw another's shape. Dated features carry their time (datetime, start_datetime/end_datetime, and a time per vertex for a moving path), which is the shape a timeline already reads β€” see docs/GEOMETRY.md. The same machinery is the SPARQL widget's Map renderer, so a query that returns coordinates is a map (auto-detected from the result, or chosen in βš™). The title bar names the place the fetch resolved (Q64 β†’ Berlin), and when the relay fails the card prints the relay's own reason with Try again rather than leaving a broken image. Open ?config=/map-demo.json.
  • Nothing proxied is the weak point β€” six services need our server rather than your browser (docs/PROXIES.md), and all six share one set of rules: a host allowlist, a streamed byte cap, a deadline, a per-client rate limit, an in-flight ceiling and bounded caches. scripts/relay-guard-e2e.mjs tries to break them on every npm test.
  • Filling the box β€” a gallery can be one file filling its card. Paste a single File: name, set Display β†’ Single image, and two switches decide the rest: Image fit (Letterbox shows the whole file, Fill crop covers the box) and the Edge to edge switch, which takes away the title bar, the padding and the card's own title so the picture reaches the edges. Edge to edge works on the 🎬 Video / Media Player too β€” its file line goes, and for a single file its transport controls with it, while the video's own controls stay β€” and on the 🌐 360Β° Panorama, the πŸ”³ QR Code, the πŸ“„ Document Reader, the πŸ“– IA Book and the πŸ“„ Wiki Page. A gallery's captions are a switch as well, so a tile can carry no text at all. A click on a picture does nothing by default β€” Clicking an image offers Open the file page in a new tab, Send it to the board, Both, or Nothing, because on a phone a click that misses what you were aiming at should not throw you onto Commons; a gallery you can click through says so in one setting. Open ?config=/image-tile-demo.json to see all four.
  • A wiki page in a card β€” the πŸ“° Wikipedia Box renders any page by title, not only a Main Page template: choose the lead (light, and dense with links), a section by number or heading name, or the whole page. Its own tables, images and references come with it β€” styled by the app rather than by Wikipedia's skin β€” and the card's footer says what it is showing. Then pick from it: arm a brush and click the blue links inside the card, and each click places a card for what you clicked instead of following the link. That loop is the reason the mode exists; open ?config=/wiki-page-demo.json to watch it.
  • πŸ‘† Clickable zones on a picture β€” a single image can carry zones: boxes in percentages of the picture, written one per line in the gallery's Clickable zones field (x,y,w,h | label | kind | value | action), each with a label, a kind and a value. A click publishes the value on that card's own zones channel, so another card loads the article, the file or the category it names β€” and because the boxes are percentages of the file, they hold at any card size. The picture's own background is not a link, so a click that misses every zone does nothing. Five boards show what that is for: a photograph whose zones are the file's own Commons notes, a biology diagram read word by word by OCR, a power-plant diagram and a lifecycle chart whose bars are the doors β€” each feeding an article and its traffic β€” and an image-to-image tour where a click loads the next view with its own zones. docs/ZONES.md has the design notes and what is still to come.
  • Pick mode β€” πŸ–Œ Pick β–Ύ arms a brush: choose a widget type, then click items inside the cards you are already reading and each click places a card for that item. One click, one card, named in the toast; the same item twice refuses rather than making a twin; Undo takes it back off. It is a second, power-user verb beside + Add Widget, not a replacement β€” the menu only offers types that can consume the thing you clicked. Clickable today: article-list rows, ranked article rows (including the expanded top-pages card), any ranking row whose own link is a Wikipedia article or a Commons file, GLAM sample strips, CIM file rows and gallery tiles β€” a row's link declares what it is.
  • Content-fit galleries β€” image galleries default to full window width and fit their height to the image count after loading, until you resize one yourself, and then your size sticks

Building a board

New here? docs/GUIDE.md explains the model β€” board params (shared inputs), widget config (local settings) and dataflow (derived values) β€” with worked examples.

  • πŸŽ›οΈ Board params β€” declare a params block, reference {{name}} in any widget. Board Controls renders buttons, menus, text fields, number sliders and month steppers, plus a lookup box that validates free text against live Wikimedia data (type any museum, and the CIM cards follow) β€” one click re-aims every referencing widget. Each card can be scoped to a subset of params, so one drives the article and another the target language.
  • πŸ”€ Widget-to-widget dataflow β€” a widget can emit its output and another can consume it, either through a source picker in the βš™ panel or {{widget:id}} interpolation in any config field; consumers re-fetch automatically when the value changes. Article Excerpt emits its first paragraph, so a Translator, Speaker or Markdown card can read the article. Try it: ?config=/flow-demo.json. Every card's βŠ• opens the two-sided spawn panel: the producers it can read from, the consumers it can feed, and the refused wires said out loud rather than dropped β€” the pairings come from the registry's own outputs/kinds, so nothing is offered that cannot work. npm run smoke:spawn proves it in a browser.
  • 🏷️ Names, not positions β€” every widget has a visible, editable instance name. Renaming one that others reference repoints them atomically, after a confirm dialog that says how many references it will update.
  • ✨ Ask β€” describe what you want in plain language ("random sampling of images from a category") and get widget recommendations with settings pre-filled, powered by Wikimedia's LiftWing LLM through a same-origin relay, with an offline keyword fallback. The catalog it sees is generated by npm test, so it cannot drift from the registry.
  • πŸ“° Live Wikipedia boxes β€” render a template with the wiki's own markup and its own styles: the five Main Page boxes (In the news, Did you know, Today's featured article, the selected anniversaries, Picture of the day) or any other, including dated ones that keep themselves current. A box's links open in a new tab; set Links in the box to send to the board and a click publishes the page the reader chose for another widget to load or draw. docs/WIRING-BOARDS.md is the wiring guide.
  • 🌍 Every wiki, chosen quickly β€” one picker for all 364 projects, ordered by the ones you use, your default wiki, a curated shortlist, then everything else; searchable by name, code, script or English name. βš™ Settings holds your wiki and the recent list.
  • Add, configure, inspect β€” a searchable catalog; a βš™ panel per widget with Apply & Reload pinned at the bottom so every setting stays reachable in a short card (the β“˜ panel likewise); asset-aware titles that say what each widget is analyzing.
  • 🎞️ Commons galleries β€” a gallery page (a main-namespace page with a <gallery> tag: hand-picked images and hand-written captions, unlike a category, which has neither) rendered as a grid or a list. The captions travel as data β€” they can feed a Translator or a Speaker β€” and a tile click publishes that file for another card once Clicking an image is set to Send it to the board.
  • πŸ”Ž One box, any wiki, validated β€” type a page name and the board follows: suggestions as you type, a βœ“/βœ— verdict against the wiki you chose, and en:/de:/commons: typed as a shortcut that moves the wiki picker. What the box stores is a reference, so the wiki travels with the page.
  • Example board β€” ✨ loads a showcase with every widget type; the guided demos are at ?config=/demos.json.

Sharing & persistence

  • Layout persistence β€” saved to localStorage (wikibento-layout), survives refresh
  • Opening someone's link borrows their board β€” a ?config= or #/d/… link is shown, never written over yours: your own board stays saved and untouched, and a slim notice offers [Save this as mine] or [Back to my board]. Edit anything and the board becomes yours; the one it displaces stays recoverable for 24 hours (docs/URL-STATE.md)
  • Export / Import β€” ⬇ downloads the whole board as dashboard.json; ⬆ loads one back (file or paste) with per-field validation, warnings, and nothing applied unless it is valid
  • Shareable links β€” πŸ”— opens a Share panel with a QR code and a copyable link. An untouched board loaded from ?config= shares that short, phone-friendly URL; anything else embeds the board itself β€” compressed (#/z/…), which is measured to make the difference between a QR code that works for 1 of 15 real boards and one that works for 13. The link always describes the board on screen rather than the file it started from, and a board too big for any QR says so and offers the paths that have no length limit (copy the link, or Export β†’ AirDrop β†’ Import on the phone)
  • βš™ Settings β€” your wiki (the default the project picker offers first), the wikis you have used recently, and whether Present asks for fullscreen. Stored in this browser, inspectable in devtools, no account
  • Reset β€” β“˜ About explains the tool; Reset returns the starter board or a blank one, in one dialog β€” and clears the URL params that pointed at the board you just discarded, so a refresh cannot bring it back

Showing and exporting

  • πŸ“– Reading a book, or a document β€” πŸ“– IA Book reads a scanned archive.org book page by page from its IIIF manifest: turn, zoom, a thumbnail strip, search inside (each hit names its page and shows the matched words boxed on it), the page's OCR text, and facing pages β€” with right-to-left handled for Arabic/Hebrew/Yiddish scans. πŸ“„ Document Reader does the same for a Commons PDF or DjVu, and where Wikisource has transcribed the file its transcription shows from the start and follows you as you turn pages, headed by the proofreading grade (Validated, or Not proofread (uncorrected OCR) when that is the truth). Both share one viewer β€” docs/DOCUMENT-VIEWER.md.
  • πŸ•°οΈ Timelines of lives β€” the SPARQL widget's timeline renderer puts dated rows on one shared axis, one lane per group, with a shaded band for the window every lane is documented in. Two alignments (calendar years β€” what happened at the same time β€” or age, aligning every lane at its first event), βˆ’ / + zoom to 8Γ— with horizontal panning, an overlap toggle, a light card theme, and an optional title (the only title in lean/present mode). Two presets ship: two-lives (Anne Frank Γ— Martin Luther King Jr.) and curie-pair (Marie Γ— Pierre, where age alignment shows his lane ending at 46 while hers runs to 67). Try ?config=/parallel-lives-demo.json.
  • – Export any widget β€” one menu, four formats: PDF (the browser's print engine β€” vector and selectable, the best artifact for a report, slide or email), CSV (the widget's own data, as rows), SVG (any widget that is not an iframe, with images inlined), and PNG wherever the pixels can reach a canvas cleanly β€” a widget that draws itself as SVG, or one whose images come from a CORS-enabled host (iiif.archive.org, upload.wikimedia.org, thumb.wikimedia.org), which is how a book or document page exports as a real PNG. PNG of an arbitrary HTML/CSS card is deliberately not offered β€” docs/EXPORT.md says why, and what to do instead.
  • πŸ–¨ Print or save the board as PDF β€” three shapes, because a dashboard is not a document: Board (the grid you see, row for row), Poster (one page, sized to the board) and Document (a card per row, to read). The sheet waits for stats and images to arrive before it is taken, and it scales the board to the paper rather than reflowing it, so nothing overlaps and nothing is cropped. Chrome is removed; each card's ⏱ freshness line is kept (docs/EXPORT.md).

Reliability

  • Freshness and time scope are visible β€” every live widget shows when it last fetched (⏱ "updated 2:34:05 PM Β· auto-refresh 1h") and the scope it resolved to ("2026-07"); the latter is enforced by npm test
  • A rate-limit-aware HTTP layer β€” 4 concurrent fetches, Retry-After honoured as a global cool-down, one retry per 429 with an actionable "wait ~Ns, then Retry", and a shared TTL cache (two widgets hitting the same 195 KB CSV cost one request, with a 15 s timeout and backoff)
  • Per-widget error boundaries β€” a crash shows a themed fallback with Try Again instead of taking the dashboard down, and the grid reflows on resize

Quickstart

npm install
npm run dev            # dev server β†’ http://localhost:5173
npm run build          # tests, then production build β†’ dist/
npm run preview        # serve dist/ at http://localhost:4173
npm test               # unit tests + the cross-document consistency gates
npm run smoke          # grid geometry + panel reachability (every βš™/β“˜ action at any panel size)
npm run smoke:boards   # a board pasted through ⬆ Import draws β€” one card per widget, no waiting state
npm run test:browsers  # the same board in Chromium + Firefox + WebKit
npm run check:board -- board.json   # what the app will say about a board from outside (or: pbpaste | npm run check:board)
npm run smoke:mcp      # the MCP endpoint, spoken to as a client (handshake, four tools, every refusal)
npm run lint           # oxlint

Also available: npm run docs-facts (the consistency gates alone; :live also checks what production serves), smoke:map (the Map card in a browser β€” resolved name, relay failure, Try again), check:map-landmarks (the map projection checked against real rendered maps), smoke:panels, smoke:qr, smoke:share, smoke:wayback, smoke:canvas and smoke:canvas-import (board ⇄ JSON Canvas, both directions), smoke:spawn (the two-sided wiring panel), guide:board and map:widgets (regenerate the served contract and the one-page widget map), render:board -- board.json (render any board in a browser and report what each card actually shows), check:layouts (no demo board overlaps itself), build:validator (bundle the validator /api/validate runs; --check fails on a stale one), and update:cim-allow-list, and audit:verify (re-derive the ImageNote audit's numbers offline β€” the matcher, the gold set and the calibration behind RECONCILIATION).

For the mobile reflow β€” what actually moves when a card above the reader changes size, and the options for it: node scripts/mobile-reflow-probe.mjs (the measured numbers are in docs/research/MOBILE-RESIZE-STABILITY.md).

Browser runs need the engines installed with the repo's own playwright-core, and some hosts need a remote-browser daemon or an explicit engine path β€” see docs/BROWSER-TESTING.md.

Project Structure

wikibento/
β”œβ”€β”€ index.html                 # entry HTML (inline SVG favicon)
β”œβ”€β”€ vite.config.js             # Vite + React plugin
β”œβ”€β”€ public/                    # static assets + dashboard.json (hosted sample config)
β”œβ”€β”€ docs/                      # the documentation set (indexed below)
β”œβ”€β”€ pipeline/  video/          # the demo-video engine, and this project's script for it
└── src/
    β”œβ”€β”€ main.jsx               # React 19 bootstrap
    β”œβ”€β”€ App.jsx                # grid, state, persistence, toolbar, URL boot
    β”œβ”€β”€ App.css                # dark theme + all component styles
    β”œβ”€β”€ components/            # AddWidgetPanel, ImportPanel, SharePanel, AboutPanel, BoardNotice, ProjectField,
    β”‚                          # SettingsPanel, ErrorBoundary
    β”œβ”€β”€ lib/
    β”‚   β”œβ”€β”€ dashboardConfig.js # format v1: example dashboard + validateDashboard()
    β”‚   β”œβ”€β”€ urlState.js        # what the URL may claim: one reader, one writer, claim integrity
    β”‚   β”œβ”€β”€ borrowedBoard.js   # borrowed vs adopted boards, the recovery stash, the notice's rule
    β”‚   β”œβ”€β”€ reference.js       # a page plus its wiki (enwiki:Weddell Sea), and the one projectβ†’host mapping
    β”‚   β”œβ”€β”€ projects.js        # every wiki (364), ordered recency β†’ default β†’ curated β†’ rest
    β”‚   β”œβ”€β”€ wikiBox.js         # rendering a wiki template: sanitise, scope, rewrite, what a click means
    β”‚   β”œβ”€β”€ speech.js          # the typed speech value ({type,text,lang}) + voice choice by language
    β”‚   β”œβ”€β”€ paramSources.js    # validated lookup sources (categories, galleries, articles, QIDs)
    β”‚   β”œβ”€β”€ markdown.js        # tiny zero-dep Markdown renderer (Text/Markdown widget)
    β”‚   β”œβ”€β”€ share.js           # URL loading/sharing (?config=, #/d/<base64>)
    β”‚   └── qr.js              # URL β†’ inline SVG QR code (qrcode-generator)
    └── widgets/
        β”œβ”€β”€ index.js           # WIDGET_TYPES registry (add a widget here)
        β”œβ”€β”€ WidgetFrame.jsx    # title bar, config panel, load/error/refresh lifecycle
        └── dataSources.js     # API fetchers (one per widget, batched where needed)

Proxied services (what runs on our server)

Almost everything WikiBento shows is fetched by your browser, from Wikimedia directly β€” the Action and REST APIs, Commons, Wikidata, the Internet Archive all send CORS headers, so no server of ours is involved. A few things cannot work that way, and those go through our own Toolforge server; the full inventory, the reasons and the rules are in docs/PROXIES.md.

Route Upstream Why it needs a server
/api/staticmap Wikimedia's map service it answers a Wikimedia User-Agent with a PNG and a browser with 403, which the browser then refuses as an image
/api/proxy Wikimedia wikis, top.hatnote.com, web.archive.org sources that send no CORS headers, and asking for a desktop parse with our own User-Agent
/api/resolve w.wiki expanding short URLs server-side; the target sends no CORS
/api/petscan PetScan quick-intersection ignores max and can return 39 MB β€” the byte cap and file budget live on the server
/api/wayback-gallery web.archive.org CDX no CORS on the CDX side
/api/ask LiftWing LLM the API key, the system prompt and the spend live server-side
/mcp (nothing β€” no upstream) the Model Context Protocol endpoint: four read-only tools (get_catalog, get_board_guide, validate_board, make_board_url) so an agent can read the board contract, write a board, check it and hand back a link without a human carrying JSON between windows β€” docs/MCP.md
/api/validate (nothing β€” no upstream) the board doctor as a service: a board goes in, the same verdict ⬆ Import gives comes out, so an outside model can check its own work. It exists server-side because the validator is the app's own code bundled, not a second implementation

Every route is https, GET, cached where an answer repeats, and bounded: a host allowlist, a streamed byte cap, a deadline, a per-client rate limit, an in-flight ceiling, and in-memory caches with a size as well as a TTL. Nothing writes to disk. node scripts/relay-guard-e2e.mjs (also npm run smoke:relay, part of npm test) starts the real server with tight limits and tries to break it β€” off-ladder map sizes, a non-allowlisted host, an oversized body, a burst of 24 requests β€” and then checks the server is still answering for someone else and its memory is still sane.

Technology Stack

Layer Choice
Framework React 19.2 (StrictMode)
Build Vite 8.2
Grid react-grid-layout 2.2.4 + react-resizable 4.0.2
Linting Oxlint (react + oxc plugins)
Charts Hand-rolled SVG (no chart library used)
QR codes qrcode-generator (client-side, zero-dep; SVG rendered in-app)
Pair-built with The Pi coding agent on DeepSeek V4

A substantial part of this project was written that way: the widget implementations and their batched fetchers, the rate-limit-aware HTTP layer, the docs↔code consistency gates (scripts/docs-facts.mjs), the widget catalog and this documentation set, and the demo-video pipeline. The agent works in a repository under review β€” every change has to pass the test suite and the docs gates, and deploys are made by hand (Andrew Lih, User:Fuzheado), who directs the work and checks the result in a browser.

Status

WikiBento is smoke-tested per widget in a real browser, against named live assets β€” the dated record, with the failures each check was written for, is docs/VERIFIED-WORKING.md. The claims are checked rather than asserted: npm test regenerates the widget manifest and enforces the cross-document consistency gates, npm run smoke enforces grid geometry and that every βš™/β“˜ action is reachable at any panel size, and npm run test:browsers loads a real board in all three engines. npm run test:browsers:demos sweeps every demo board in Chromium, Firefox and WebKit at desktop and phone widths, asserting that each card renders, that none collapses, that no card shows a bare β€” in place of data and that no ranking is empty β€” checks written for the bugs they then found (an empty box on iPhone, a pageviews card with no count, a 360Β° widget that had never rendered; ISSUE-100).

An agent can write a board and check it itself. /mcp speaks the Model Context Protocol β€” four read-only tools (get_catalog, get_board_guide, validate_board, make_board_url) β€” so Claude or ChatGPT can read the contract, write a board, validate it against the app's own rules and hand back a link, with no JSON carried by hand (docs/MCP.md); npm run smoke:mcp drives the protocol as a client (16 checks, in npm test).

A board written outside WikiBento is checked twice, because "validates" and "renders" are different claims: npm run check:board -- board.json (and the served /api/validate, which is the same checker) reports what a board from a chat will do β€” what blocks it, what the app will repair silently, what it will quietly ignore β€” and npm run smoke:boards pastes boards through the app's own ⬆ Import panel and requires a card per widget with nothing left waiting for a value. Both exist because that path had a defect nothing checked: Import silently dropped a board's params, so every {{param}} card sat waiting, and the demo sweep never noticed because no demo board arrives that way. The feature E2Es drive the reading and URL behaviour in a real browser (smoke:iabook, smoke:document, smoke:url, and smoke:map β€” four ways into a map card, including geometry over the wire) β€” and the map geometry has its own check against real rendered maps and a live Wikidata geoshape (check:map-landmarks: the projection, the card transform, and a drawn shape classified inside and out). What each of them caught is in docs/BROWSER-TESTING.md. The same suite holds the dataflow contract honest: an emitter that publishes prose must declare where it came from, a widget with named channels must say what its bare id means, a channel a board references must exist, and no widget may hard-code a language list.

Documentation

Every document, grouped by what you would want it for. The long-form material lives here β€” the README is the front door.

  • Start here β€” GUIDE (the model + cookbook) Β· TUTORIAL (build a board step by step, then store it on a wiki) Β· EXPORT (getting data, a PDF or the whole board out β€” and why PNG waits) Β· WIDGET-CATALOG (every widget type, with its API) Β· WIDGET-MAP (every type on one page, grouped by what you must supply β€” with the gates and the wiring; the picture is widget-map.pdf) Β· JSON-FORMAT (board spec v1 + schema) Β· SCREENSHOTS (dated snapshots of real boards)
  • Let an agent do it β€” the same four capabilities are an MCP server at /mcp (get_catalog, get_board_guide, validate_board, make_board_url), so Claude or ChatGPT can read the contract, write a board, check it and return a link β€” docs/MCP.md. And locally: npm run check:board -- board.json runs the app's own validation over a board that came from a chat, a notebook or another tool: what will block the import, what the app will repair silently, what it will quietly ignore (docs/JSON-FORMAT.md is the contract it checks). The same checker is served as /api/validate (GET with the board in the URL, or POST it), so a model can call it and fix its own board before handing it over; the contract it checks is /board-guide.md.
  • Build & extend β€” WIRING-BOARDS (what travels between cards: sources, consumers, channels, references β€” the advanced guide after the GUIDE) Β· ARCHITECTURE Β· WIDGET-DEVELOPMENT Β· BOARD-COMPOSITION (every widget, wired) Β· DATA-SOURCES (every endpoint, cap and gotcha) Β· GEOMETRY (paths, polygons and time: GeoJSON in and out, the intake rules, and why none of it is a dialect) Β· MODULARITY-AND-DATAFLOW Β· MEDIA-DATAFLOW Β· SCALABILITY Β· ASK-ARCHITECTURE Β· INTENT-BENCHMARK Β· ZONES (shipped for flat images, 2026-10-09; the editor, the import and the 360Β° half remain β€” the HyperCard button layer, the Commons and Wikidata formats that already carry the areas, the crop rule that never hides a zone, how a file's own Commons notes become zones (and how their labels become targets, measured), tours that move the card and choose where a jump lands, the editor, and the honest cost; the sphere wireframe is a live Pannellum render and the arrival one is real equirectangular geometry; the build is sequenced in five slices: A has shipped, B–E remain, each with its own gate) Β· RECONCILIATION (turning a label into a target β€” and no longer only a plan: the corpus measurements, a working four-stage matcher, a 100-note gold set and the calibrated publish gate are in the repo, and npm run audit:verify re-derives every number from cached API responses, offline, in ~45 s. What is still to build is the review UI and the one resolver the page box, params and the Ask door would share)
  • Vocabulary β€” GLOSSARY (the words this project uses, and the ones it avoids β€” what edge to edge means, why full bleed is only a synonym, and the retired spellings that still resolve)
  • Behaviour β€” URL-STATE (what the address bar may claim, decided action by action, and the audit that enforces it: npm run smoke:url)
  • Ideas β€” WIDGET-IDEAS Β· DEMO-IDEAS Β· ROADMAP Β· ISSUES Β· LIFELINE-WIDGET (timelines of lives, and comparing two of them β€” measured Wikidata-vs-prose coverage, what shipped, what is next)
  • Testing & ops β€” BROWSER-TESTING (the suites + engine-install traps) Β· VERIFIED-WORKING Β· DEPLOYMENT and the deploy log Β· AGENT-MEMO Β· BUG-REPORT-ios-safari-fetch Β· AUTHORS
  • Tutorial video β€” TUTORIAL-VIDEO-TOOLING (the pipeline: state, how to re-run, what is missing; plus the ecosystem research behind it) Β· pipeline/README (the reusable engine)

Feedback & Feature Requests

Thumbnails are their own subject: sizes, the pre-rendered bucket ladder, and which bytes a card actually fetches β€” docs/THUMBNAILS.md, which also points at the sister project that measured it.

Requests that get picked up are tracked with design notes in docs/ISSUES.md and docs/ROADMAP.md, so you can watch an idea become a widget.

License

Wikimedia-oriented demo dashboard. Data comes from Wikimedia APIs (CC BY-SA 4.0 content licensing applies to any downstream use of article content; pageview and stat aggregates are public statistics).

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages