on cd590f3 help sign in
9 files changed+41−293
since splitsnapshots
unifiedsplit
README.md+28−191 desk/app/shelf.hoon−27 desk/desk.docket-0+1−2 desk/lib/shelf.hoon+5−18 desk/mar/svg.hoon−12 desk/tests/lib/shelf.hoon+2−31 desk/web/favicon.svg−1 desk/web/shelf-icon.svg−1 flake.nix+5−10
README.md+28−191
@@ -1,5 +1,3 @@
1−<img src="desk/web/shelf-icon.svg" width="96" height="96" alt="Shelf logo">
2−
31# %shelf
42
53A public web page that lists the apps your Urbit ship publishes, so anyone can
@@ -9,42 +7,29 @@ The page shows your ship's `%treaty` alliance: your own published desks plus
97any desks you republish from other ships. Each app has an Install button that
108opens it in the visitor's own Landscape, and the Dojo `|install` command as a
119fallback. The page follows the visitor's light or dark preference. It has no
12−JavaScript except for the Copy buttons. The same URL also returns the list as
13−JSON.
10+JavaScript except for the Copy buttons.
1411
1512%shelf answers on exactly one path, `/shelf`. Run it behind a reverse proxy
1613that serves it at the root of a dedicated domain (see
17−[Serving it on its own domain](#serving-it-on-its-own-domain)). The page's
18−links and its install form all point at `/`.
14+[Serving it on its own domain](#serving-it-on-its-own-domain)). All of the
15+page's links point at `/`.
1916
2017## Requirements
2118
2219- Kernel `[%zuse 408]`.
23−- `%landscape`, which provides `%treaty` and `%docket`. Fresh ships have it.
24−- Nix, for the one-command build, if you install from source. Without it,
25− copy `desk/` from a checkout.
20+- `%landscape`, which provides `%treaty`. Fresh ships have it.
2621
2722## Install
2823
29−Install %shelf from ~donmec-sallud, which publishes it:
30−
31−```
32−~sampel-palnet:dojo> |install ~donmec-sallud %shelf
33−```
34−
35−%shelf then gets its updates from ~donmec-sallud.
36−[shelf.iko.soy](https://shelf.iko.soy) is %shelf running on that ship. It
37−lists all the apps ~donmec-sallud offers, %shelf among them.
38−
39−To install from source instead, build the desk:
24+Build the desk with Nix. `result` is then the desk folder:
4025
4126```sh
4227nix build github:iko-soy/shelf
4328```
4429
45−`result` is now the desk folder (see [Building with Nix](#building-with-nix)).
46−Without Nix, use `desk/` from a checkout in its place. Then, with your ship
47−running:
30+From a checkout, `nix build` does the same, or you can use `desk/` directly.
31+
32+Then, with your ship running:
4833
4934```
5035~sampel-palnet:dojo> |new-desk %shelf
@@ -61,59 +46,18 @@ chmod -R u+w /path/to/pier/shelf # files from the Nix store are read-only
6146~sampel-palnet:dojo> |install our %shelf
6247```
6348
64−Either way, once %shelf runs, the page is at `https://<your-ship>/shelf`, and
65−Landscape shows a Shelf tile that opens it. It is meant to be opened through
66−your own domain, though: on the ship's address the page renders, but its links
67−lead to `/`, which belongs to Landscape.
68−
69−## Building with Nix
70−
71−`flake.nix` has one output: the desk, `packages.<system>.desk`, which is also
72−the default package. Building it copies `desk/` into the Nix store; nothing is
73−compiled, because Clay compiles the Hoon on your ship when you commit the desk.
74−
75−| Command | Builds |
76−|---|---|
77−| `nix build github:iko-soy/shelf` | the latest commit on the default branch |
78−| `nix build github:iko-soy/shelf/<commit>` | a specific commit |
79−| `nix build` | your checkout, from the repo root |
80−
81−Each one leaves a `result` symlink to the desk in the store:
82−
83−```
84−result/
85−├── desk.bill desk.docket-0 sys.kelvin
86−├── app/ gen/ lib/ mar/ sur/ tests/
87−└── web/
88−```
89−
90−- Files in the store are read-only, so `chmod -R u+w` the mount after copying
91− them in: the ship has to be able to write to its own mount.
92−- `nix flake check --all-systems` checks the flake on every system it
93− supports: `x86_64-linux`, `aarch64-linux` and `aarch64-darwin`. Plain
94− `nix flake check` checks only your own. Current nixpkgs no longer supports
95− Intel Macs.
96−- nixpkgs is only used for `runCommandLocal`. `nix flake update` moves its
97− pin in `flake.lock`.
98−- From another flake, for example a NixOS config that sets up a ship, the desk
99− is:
100−
101− ```nix
102− inputs.shelf.url = "github:iko-soy/shelf";
103− # …
104− shelf.packages.${system}.desk
105− ```
49+The page is now at `https://<your-ship>/shelf`, and Landscape shows a Shelf
50+tile that opens it. It is meant to be opened through your own domain, though:
51+on the ship's address the page renders, but its links lead to `/`, which
52+belongs to Landscape.
10653
10754## Update
10855
109−A %shelf installed from ~donmec-sallud gets its updates from that ship.
56+Build and copy the desk into the mount again, as above, and run
57+`|commit %shelf`. Then check that `.^(cass:clay %cw /=shelf=)` moved: if the
58+new version doesn't build, Clay keeps the old one and logs nothing.
11059
111−To update a source install, build and copy the desk into the mount again, as
112−above, and run `|commit %shelf`. Then check that `.^(cass:clay %cw /=shelf=)`
113−moved: if the new version doesn't build, Clay keeps the old one and logs
114−nothing.
115−
116−A source install comes from your own ship, so it gets no over-the-air updates.
60+%shelf is installed from your own ship, so it gets no over-the-air updates.
11761After a kernel upgrade, Clay holds it until `desk/sys.kelvin` lists the new
11862`%zuse` version; update that file (or pull a release that has) and commit.
11963`+vats %shelf` shows whether it is running or held.
@@ -132,9 +76,7 @@ before you can publish it.
13276 `:treaty|unally ~other-ship`.
13377
13478%shelf has a docket of its own, so `:treaty|publish %shelf` lists it on its
135−own page, where others can install it. The docket carries the app icon
136−inline, as a URL-encoded `data:` URL, so the icon shows in Landscape and on
137−other ships' pages with nothing to host.
79+own page, where others can install it.
13880
13981A republished app appears once your ship has received its docket from the
14082publisher, which happens a few seconds after you ally them. Apps are sorted by
@@ -161,7 +103,7 @@ apps.example.com {
161103- `redir` sends every path except `/` back to `/`, so nothing else on your
162104 ship can be reached through this domain.
163105- `rewrite` replaces only the path, so the query string the page relies on
164− (`/?install=…`, `/?asset=…`) reaches the ship intact.
106+ (`/?install=…`) reaches the ship intact.
165107- `Forwarded` tells Eyre the visitor came over HTTPS. Eyre trusts it only
166108 from `127.0.0.1`, hence the IP instead of `localhost`. Without it, a ship
167109 that also serves its own TLS redirects every proxied request to its HTTPS
@@ -171,10 +113,6 @@ apps.example.com {
171113 another proxy, pass `Host` through (nginx: `proxy_set_header Host $host;`)
172114 and send `Forwarded` (or `X-Forwarded-Proto`) the same way.
173115- Your ship's own domain is unaffected: `/` still goes to Landscape.
174−- %shelf serves everything the page needs from the desk, at the same URL: the
175− CSS and JavaScript inline, and the favicon at `/?asset=favicon.svg`. It
176− also serves the app icon at `/?asset=shelf-icon.svg`, though the docket
177− carries that inline. Nothing else needs to be reachable.
178116
179117This config was tested with Caddy 2.11 in front of a fake ship.
180118
@@ -217,15 +155,12 @@ curl -H 'Accept: application/json' https://apps.example.com/
217155- `image` is `null` when the docket has none, and `website` is `""`.
218156- `image` and `website` are the docket's values as they are. Dockets can come
219157 from other ships, and unlike the page, the JSON doesn't filter these, so
220− check them before using them. `image` may also be an inline
221− `data:image/…` URL.
222−- Text that isn't valid UTF-8 comes out with U+FFFD in its place, so the
223− listing is always valid JSON.
158+ check for `http(s)` before using them.
224159- Entries are sorted like the page. Key order within an object is not fixed.
225160- JSON wins when the header ranks `application/json` above `text/html`. When
226161 the two rank equally, the more specific match wins, so
227162 `application/json, */*` gets JSON. A full tie, a bare `*/*` or a missing
228− header gets HTML. Repeated `Accept` lines count as one list.
163+ header gets HTML.
229164- The response has `Access-Control-Allow-Origin: *`, varies on `Accept` and
230165 `Origin`, and is `private, max-age=60`. It is private because Eyre attaches
231166 its session cookie to every response, and on your own logged-in requests
@@ -237,10 +172,10 @@ curl -H 'Accept: application/json' https://apps.example.com/
237172 URL (for example `sampel-palnet.tlon.network`) and shows the Dojo command.
2381732. The form is POSTed. %shelf checks the URL and keeps only its scheme, host
239174 and port. It remembers that origin in a cookie for a year, then redirects
240− to `<ship>/apps/landscape/search/direct/apps/~publisher/desk`. That is
175+ to
176+ `<ship>/apps/landscape/search/direct/apps/~publisher/desk`. That is
241177 Landscape's own "Get App" dialog. The visitor's ship fetches the app's
242− details directly from the publisher, and a visitor who isn't logged in
243− lands there after their ship's login page.
178+ details directly from the publisher.
2441793. On later visits, Install links straight there. The button reads
245180 `Install on ~sampel-palnet` when the URL's first label is a planet name
246181 (or a moon or comet), or any ship name on `arvo.network`, `tlon.network`
@@ -256,9 +191,8 @@ Guards:
256191 docket image URL pointing at your domain, can plant a URL in a visitor's
257192 cookie or bounce them elsewhere through your domain.
258193- Only apps actually on the shelf can be installed through the form.
259−- On the page, docket images are used only if they are `http(s)` or
260− `data:image/…` URLs, and website links only if they are `http(s)`.
261− Dockets can come from other ships.
194+- On the page, image and website URLs from dockets are used only if they are
195+ `http(s)`. Dockets can come from other ships.
262196
263197## Traffic
264198
@@ -289,9 +223,6 @@ after %shelf stops. Until you remove it, `/shelf` answers 503 at once.
289223 Landscape. This removes the tile and suspends the agent; a plain
290224 `|uninstall %shelf` would leave a suspended tile behind. Your `head`/`body`
291225 HTML is kept, and `|install our %shelf` brings the page straight back.
292− Uninstalling a %shelf installed from ~donmec-sallud also stops its updates.
293− To turn them back on, run `|install ~donmec-sallud %shelf` after
294− `|install our %shelf`.
2952263. To delete that state too: `|nuke %shelf, =desk &` (answer `y`).
2962274. Optionally, free the path. Only the binding's owner can remove it, so take
297228 it over from the dojo, then remove it:
@@ -304,116 +235,22 @@ after %shelf stops. Until you remove it, `/shelf` answers 503 at once.
304235 Installing %shelf again binds `/shelf` again. %shelf re-binds every time
305236 it loads, so it also restores the binding if someone removed it by hand.
306237
307−## Changes from the design handoffs
308−
309−%shelf follows two design handoffs: the page itself (layout, copy, behaviour,
310−JSON, a skeleton agent) and a later icon update. Rendered with the mock's sample
311−data, the page matches the design mock pixel for pixel in light and dark mode,
312−apart from the icons and names. Everything below differs from the handoffs,
313−and why.
314−
315−**Serving**
316−- **One path, `/shelf`, instead of `/`.** On most ships Landscape's `%docket`
317− owns `/`, and Eyre doesn't refuse a second claim: it silently hands `/`
318− over, which would take away the owner's login page. A dedicated domain
319− mapped onto `/shelf` gives visitors a clean root URL instead.
320−- **One URL for everything.** Because only `/` reaches the ship through that
321− domain, the CSS and JavaScript are inlined (not `/shelf.css`, `/copy.js`),
322− the icons are at `/?asset=…` (not `/favicon.svg`, `/shelf-icon.svg`), the
323− install form posts to `/` (not `/install`), and Change ship is `/?forget`
324− (not `/forget`). Other paths are the proxy's job; the ship answers them
325− with 404.
326−- **JSON by `Accept` header at the same URL, not `/apps.json`, and
327− `private`.** The single URL rules out `/apps.json`, and `public` caching
328− could hand an owner's login cookie to a shared cache.
329−- **HTML varies on `Cookie` as well as `Accept`.** Otherwise a browser shows
330− its cached copy for up to a minute after Change ship or a save.
331−- **Bodies go out in 16 KiB pieces**, because a single large response gets
332− quadratically slower on its way out of the ship.
333−
334−**Installing**
335−- **The install form is a POST, accepted only from the page itself, and only
336− for apps on the shelf.** A GET could be triggered by a link or by another
337− publisher's docket image, planting a phishing URL in visitors' cookies.
338−- **Deep link:** `<ship>/apps/landscape/search/direct/apps/~pub/desk`,
339− Landscape's own "show install" route, tested end to end. The handoff's
340− `…/leap/search/…` also works, but only through a redirect.
341−- **Saved ship URLs keep only scheme, host and port**, so a pasted
342− `…/apps/groups` doesn't break the deep link. `localhost` and `127.*`
343− default to `http://`, and ports must be 1–65535.
344−- **The cookie holds the URL as is (not URL-encoded), `HttpOnly`, and gets
345− `Secure` when the visitor came over HTTPS**, as `Forwarded` or
346− `X-Forwarded-Proto` says behind a proxy. The normalized URL only contains
347− cookie-safe characters.
348−- **"Install on ~ship" needs a planet or longer name, or a known ship host.**
349− The handoff's rule (any first DNS label that parses as an `@p`) turned
350− `dev.example.com` into "Install on ~dev".
351−- **Returning visitors see "Installs open on ~ship. Change ship"** in the
352− masthead, a muted link that clears the cookie. The handoff left this open.
353−
354−**Reading the alliance**
355−- **Own desks' treaties are built from Clay**, from each desk's
356− `desk.docket-0`, as `%treaty` does. `%treaty`'s `/treaty/<ship>/<desk>`
357− scry only covers other ships' desks and crashes for your own.
358−- **Only scries that can't fail.** The handoff wrapped scries in `mule`, but
359− inside an agent `mule` can't catch a failed scry: the request just fails.
360−- **Visitors arrive under an Eyre guest identity**, so the owner check
361− applies only to the settings pokes. Checking every request would 500 for
362− all visitors.
363−- **Docket text from other ships is handled defensively:** images only
364− `http(s)` or `data:image/…`, links only `http(s)`, invalid UTF-8 replaced
365− in the JSON, and a title that starts with a control character still
366− renders.
367−
368−**Page**
369−- **Header and rows share one grid (CSS subgrid)**, so columns stay aligned
370− when Install labels get wider for a saved ship.
371−- **Narrow screens:** with a saved ship the stacked layout starts at 1040px
372− instead of 860px, and the install button's text wraps; the URL field gets
373− its own line; long words wrap instead of overflowing.
374−- **Keyboard focus inside a row tints it**, as the handoff's interaction table
375− says (its CSS only did this on hover).
376−- **"1 app"**, not "1 apps".
377−- **Republished apps say "from ~publisher"**, not "by ~publisher".
378−- **The sigil is drawn in Hoon** by a vendored renderer, in `currentColor` on
379− `var(--ink)` so it inverts with the theme, at sigil-js's 56px geometry.
380− Moons and comets get the plain square, as in Landscape.
381−- **New: owner-set raw HTML in `<head>` and `<body>`**, for analytics.
382−
383−**Icons and docket** (the icon update)
384−- **The docket's `image` is the icon inline, as a URL-encoded `data:` URL**,
385− instead of an absolute URL on the ship, which would need filling in per
386− ship and would break wherever that ship can't be reached.
387−- **The tile opens `/shelf`** (`site+/shelf`, not `site+/`), and the docket's
388− website is this repo.
389−- **The docket's description drops "with one-click install"**: first-time
390− visitors enter their ship's URL before the Install button takes them there.
391−- **`mar/svg` is the upstream mark from `%base`**, not the handoff's new one.
392−- **Icons are served `private`** for the same login-cookie reason as the
393− JSON.
394−
395−**Other**
396−- **Kernel `[%zuse 408]`**, the current release, replacing the handoff's
397− placeholder 409.
398−
399238## Development
400239
401240The desk is self-contained. `desk/` holds everything Clay needs:
402241
403242| Path | What |
404243|---|---|
405−| `desk.docket-0` | What Landscape shows: title, description, color, icon, version, website, license. Its `image` is `web/shelf-icon.svg` URL-encoded; a test checks they match |
244+| `desk.docket-0` | What Landscape shows: title, description, color, version, website, license |
406245| `app/shelf.hoon` | The agent: Eyre binding, routing, scries, responses |
407246| `lib/shelf.hoon` | Pure helpers and the page (Sail) |
408247| `sur/shelf.hoon`, `mar/shelf/action.hoon`, `gen/shelf/` | The `head`/`body` settings |
409248| `web/shelf.css`, `web/copy.js` | Inlined into the page at build time with `/*` |
410−| `web/shelf-icon.svg`, `web/favicon.svg` | The app icon (also inlined in the docket) and the favicon, served at `?asset=` |
411249| `tests/lib/shelf.hoon` | Unit tests, including the rendered page |
412250| `lib/sigil.hoon`, `lib/sigil/symbols.hoon` | Vendored [sigil renderer](https://github.com/Fang-/suite) (MIT, ~paldev; shapes from `@urbit/sigil-js`, MIT, Tlon) |
413−| other `lib/`, `mar/`, `sur/` files | Copied unmodified from `%base` (urbit/urbit `5a187fe`, including `mar/svg`) and `%landscape` (`sur/treaty`, `sur/docket`, `lib/docket`, `mar/docket-0`; tloncorp/landscape `46c6ed8`) |
251+| other `lib/`, `mar/`, `sur/` files | Copied unmodified from `%base` (urbit/urbit `5a187fe`) and `%landscape` (`sur/treaty`, `sur/docket`, `lib/docket`, `mar/docket-0`; tloncorp/landscape `46c6ed8`) |
414252
415−Outside the desk, `flake.nix` and `flake.lock` define the Nix build (see
416−[Building with Nix](#building-with-nix)).
253+`flake.nix` makes `nix build` produce the desk as `result`.
417254
418255Run the tests on a ship with the desk installed:
419256
desk/app/shelf.hoon−27
@@ -7,9 +7,6 @@
77:: Meant to sit behind a reverse proxy that maps a dedicated domain's
88:: / onto /shelf, so the page's links all point at /.
99::
10−:: It also serves its two icons from the desk, at ?asset=favicon.svg
11−:: and ?asset=shelf-icon.svg.
12−::
1310:: The owner can append raw HTML to <head> and <body>, for analytics:
1411:: :shelf|head '<script defer src="…"></script>'
1512:: :shelf|body '…'
@@ -18,8 +15,6 @@
1815/+ default-agent, dbug, server, *shelf
1916/* shelf-css %css /web/shelf/css
2017/* copy-js %js /web/copy/js
21−/* favicon %svg /web/favicon/svg
22−/* shelf-icon %svg /web/shelf-icon/svg
2318|%
2419+$ card card:agent:gall
2520+$ state-0
@@ -117,7 +112,6 @@
117112 ?. ?=(?(%'GET' %'HEAD') method.request.req)
118113 [[405 ~[['allow' 'GET, HEAD, POST']]] ~]
119114 =/ q=(map @t @t) (malt args.rl)
120− ?^ name=(~(get by q) 'asset') (asset u.name)
121115 ?: (~(has by q) 'forget') (forget hs secure)
122116 ?: (wants-json (header-all 'accept' hs)) listing-json
123117 (page q hs)
@@ -133,27 +127,6 @@
133127 ^- simple-payload:http
134128 [[code ~[['location' loc] ['cache-control' 'no-store']]] ~]
135129::
136−:: +asset: one of the icons in the desk's /web
137−::
138−:: private, like the JSON: Eyre adds its session cookie to every
139−:: response, and for a logged-in owner that is their login token.
140−::
141−++ asset
142− |= name=@t
143− ^- simple-payload:http
144− =/ file=(unit @)
145− ?+ name ~
146− %'favicon.svg' `favicon
147− %'shelf-icon.svg' `shelf-icon
148− ==
149− ?~ file (plain 404 'Not found')
150− :_ `(as-octs:mimes:html u.file)
151− :- 200
152− :~ ['content-type' 'image/svg+xml']
153− ['cache-control' 'private, max-age=86400']
154− ['x-content-type-options' 'nosniff']
155− ==
156−::
157130:: +forget: clear the visitor's saved ship
158131::
159132:: Only when the browser is navigating here, not when a docket's image
desk/desk.docket-0+1−2
@@ -1,7 +1,6 @@
11:~ title+'Shelf'
2− info+'A public page of the apps this ship publishes.'
2+ info+'A public page listing the apps this ship publishes, so anyone can install them on their own ship.'
33 color+0xe8.801f
4− image+'data:image/svg+xml,%3Csvg%20height%3D%22120%22%20viewBox%3D%220%200%20120%20120%22%20width%3D%22120%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m0%200h120v120h-120z%22%20fill%3D%22%23e8801f%22%2F%3E%3Cg%20fill%3D%22%23151514%22%3E%3Cpath%20d%3D%22m24%2052h20v20h-20z%22%2F%3E%3Cpath%20d%3D%22m50%2052h20v20h-20z%22%2F%3E%3Cpath%20d%3D%22m76%2040h20v20h-20z%22%2F%3E%3Cpath%20d%3D%22m24%2072h72v8h-72z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E'
54 site+/shelf
65 version+[0 1 0]
76 website+'https://github.com/iko-soy/shelf'
desk/lib/shelf.hoon+5−18
@@ -230,18 +230,6 @@
230230 ?. |(=("https://" t) =("http://" (scag 7 t))) ~
231231 `url
232232::
233−:: +image-url: a URL fit for an <img src>: http(s), or an inline image
234−::
235−:: A docket may carry its icon as a data:image/ URL, so it shows
236−:: without a server. An <img> can't run script, whatever it loads.
237−::
238−++ image-url
239− |= url=@t
240− ^- (unit @t)
241− ?^ (web-url url) `url
242− ?. =("data:image/" (cass (trip (end [3 11] url)))) ~
243− `url
244−::
245233:: +header: a request header, matching its name case-insensitively
246234::
247235++ header
@@ -551,11 +539,10 @@
551539 :~ ;meta(charset "utf-8");
552540 ;meta(name "viewport", content "width=device-width, initial-scale=1");
553541 ;meta(name "color-scheme", content "light dark");
554− :: query-relative, so it resolves on the proxy's / and the
555− :: ship's /shelf alike; an explicit icon also stops browsers
556− :: asking the proxy for /favicon.ico, a second page render
542+ :: no favicon, so browsers don't ask the proxy for /favicon.ico,
543+ :: which it would answer with a second render of this page
557544 ::
558− ;link(rel "icon", type "image/svg+xml", href "?asset=favicon.svg");
545+ ;link(rel "icon", href "data:,");
559546 ;title: {me} · Apps
560547 ;style: {(trip css)}
561548 ==
@@ -626,7 +613,7 @@
626613 =/ pub=tape (scow %p ship.t)
627614 =/ cmd=tape (install-cmd ship.t desk.t)
628615 =/ is-open=? =(open `[ship.t desk.t])
629− =/ img=(unit @t) (biff image.d image-url)
616+ =/ img=(unit @t) (biff image.d web-url)
630617 =/ site=(unit @t) ?:(=('' website.d) ~ (web-url website.d))
631618 ;article.app(id (anchor ship.t desk.t))
632619 ;div.app-row
@@ -638,7 +625,7 @@
638625 ;div.app-name
639626 ;strong: {(trip title.d)}
640627 ;* ?: =(ship.t our) ~
641− :~ ;span.app-by: from {pub}
628+ :~ ;span.app-by: by {pub}
642629 ==
643630 ==
644631 ;div.app-desc
desk/mar/svg.hoondeleted−12
@@ -1,12 +0,0 @@
1−|_ dat=@
2−++ grow
3− |%
4− ++ mime [/image/'svg+xml' (as-octs:mimes:html dat)]
5− --
6−++ grab
7− |%
8− ++ mime |=([p=mite q=octs] q.q)
9− ++ noun @
10− --
11−++ grad %mime
12−--
desk/tests/lib/shelf.hoon+2−31
@@ -1,7 +1,5 @@
11/- docket, treaty
22/+ *test, *shelf
3−/* icon %svg /web/shelf-icon/svg
4−/* dok %docket-0 /desk/docket-0
53|%
64:: fixtures for the page tests
75::
@@ -19,13 +17,6 @@
1917 ~ [0 4 2] 'https://github.com/bus/marginalia' 'MIT'
2018 ==
2119::
22−++ shelfapp
23− ^- treaty:treaty
24− :* ~zod %shelf da+~2026.1.1 0v4
25− %1 'Shelf' 'Its own icon.' 0xe8.801f [%site /shelf]
26− `'data:image/svg+xml,%3Csvg%3E' [0 1 0] 'https://github.com/iko-soy/shelf' 'MIT'
27− ==
28−::
2920++ oddity
3021 ^- treaty:treaty
3122 :* ~zod %oddity da+~2026.1.1 0v3
@@ -61,8 +52,7 @@
6152 (expect !>((has doc "<title>~zod · Apps</title>")))
6253 (expect !>((has doc "<style>CSS</style>")))
6354 (expect !>((has doc "<main class=\"page\">")))
64− %- expect !>
65− (has doc "<link rel=\"icon\" type=\"image/svg+xml\" href=\"?asset=favicon.svg\" />")
55+ (expect !>((has doc "<link rel=\"icon\" href=\"data:,\" />")))
6656 (expect !>((has doc "<script>JS</script>")))
6757 ==
6858::
@@ -70,7 +60,7 @@
7060 =/ doc (render ~zod ~[harbor marginalia oddity] ~ | ~)
7161 ;: weld
7262 (expect !>((has doc "<span class=\"count\">3 apps</span>")))
73− (expect !>(=(1 (count doc "from ~bus"))))
63+ (expect !>(=(1 (count doc "by ~bus"))))
7464 (expect !>((has doc "<section class=\"apps\" aria-label=\"Published apps\">")))
7565 (expect !>((has doc "&lt;Odd &amp; &quot;Quoted&quot;&gt; App")))
7666 (expect !>((has doc "a &lt; b &amp; c &gt; &#39;d&#39;")))
@@ -90,10 +80,6 @@
9080 (expect !>((has doc "New to Urbit?\0a<a href=\"https://urbit.org\"")))
9181 ==
9282::
93−++ test-ui-inline-icon
94− =/ doc (render ~zod ~[shelfapp] ~ | ~)
95− (expect !>((has doc "<img src=\"data:image/svg+xml,%3Csvg%3E\" alt=\"\" loading=\"lazy\" />")))
96−::
9783++ test-ui-one-app
9884 =/ doc (render ~zod ~[harbor] ~ | ~)
9985 (expect !>((has doc "<span class=\"count\">1 app</span>")))
@@ -207,13 +193,6 @@
207193 (expect-eq !>('') !>((clean-utf8 '')))
208194 ==
209195::
210−:: the docket's inline icon is web/shelf-icon.svg, URL-encoded
211−::
212−++ test-docket-icon
213− %+ expect-eq
214− !>(`(unit @t)``(crip (weld "data:image/svg+xml," (en-urlt:html (trip icon)))))
215− !>(image.docket.dok)
216−::
217196++ test-treaty-json
218197 =/ one (en:json:html (treaty-json harbor))
219198 =/ two (en:json:html (treaty-json marginalia))
@@ -303,14 +282,6 @@
303282 (expect-eq !>(`(unit @t)`~) !>((web-url '//evil.com/x')))
304283 (expect-eq !>(`(unit @t)`~) !>((web-url 'data:image/png;base64,AAAA')))
305284 (expect-eq !>(`(unit @t)`~) !>((web-url '')))
306− (expect-eq !>(`(unit @t)``'data:image/png;base64,AAAA') !>((image-url 'data:image/png;base64,AAAA')))
307− (expect-eq !>(`(unit @t)``'https://a.b/i.png') !>((image-url 'https://a.b/i.png')))
308− %+ expect-eq !>(`(unit @t)``'data:image/svg+xml,%3Csvg%3E')
309− !>((image-url 'data:image/svg+xml,%3Csvg%3E'))
310− ::
311− (expect-eq !>(`(unit @t)``'DATA:Image/png;base64,AA') !>((image-url 'DATA:Image/png;base64,AA')))
312− (expect-eq !>(`(unit @t)`~) !>((image-url 'data:text/html,%3Cscript%3E')))
313− (expect-eq !>(`(unit @t)`~) !>((image-url 'javascript:alert(1)')))
314285 ==
315286::
316287++ test-header
desk/web/favicon.svgdeleted−1
@@ -1 +0,0 @@
1−<svg height="32" viewBox="0 0 120 120" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h120v120h-120z" fill="#e8801f"/><g fill="#151514"><path d="m13.2 49.6h26v26h-26z"/><path d="m47 49.6h26v26h-26z"/><path d="m80.8 34h26v26h-26z"/><path d="m13.2 75.6h93.6v10.4h-93.6z"/></g></svg>
desk/web/shelf-icon.svgdeleted−1
@@ -1 +0,0 @@
1−<svg height="120" viewBox="0 0 120 120" width="120" xmlns="http://www.w3.org/2000/svg"><path d="m0 0h120v120h-120z" fill="#e8801f"/><g fill="#151514"><path d="m24 52h20v20h-20z"/><path d="m50 52h20v20h-20z"/><path d="m76 40h20v20h-20z"/><path d="m24 72h72v8h-72z"/></g></svg>
flake.nix+5−10
@@ -10,15 +10,10 @@
1010 in
1111 {
1212 # The desk: copy it into a mounted %shelf desk and |commit it.
13− packages = forAllSystems (pkgs:
14− let
15− desk = pkgs.runCommandLocal "shelf-desk" { } ''
16− cp -r ${./desk} $out
17− '';
18− in
19− {
20− inherit desk;
21− default = desk;
22− });
13+ packages = forAllSystems (pkgs: {
14+ default = pkgs.runCommandLocal "shelf-desk" { } ''
15+ cp -r ${./desk} $out
16+ '';
17+ });
2318 };
2419}