commit d754334f47fa8bbbb7701a0d02362f7c1328c700 Author: Colby Russell <7b6fd8997d69edd3@x.colbyrussell.com> Date: Wed Sep 9 23:30:56 2026 -0500 Init with initial deployment diff --git a/1/index.html b/1/index.html new file mode 100644 index 0000000..dffd967 --- /dev/null +++ b/1/index.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/demo/watchlib.js b/demo/watchlib.js new file mode 100644 index 0000000..102b0a2 --- /dev/null +++ b/demo/watchlib.js @@ -0,0 +1,400 @@ +function main(event, page = event.target.body) { + ;;; check_for_json_file_name: ;;; + + let name = page.ownerDocument.location.pathname; + if (!name.endsWith(".json") && !name.endsWith(".json.html")) { + logDiagnostic("not treated as JSON: " + name); + return null; + } + + + + ;;; json_extraction_and_parsing: ;;; + + // assert(page.ownerDocument.compatMode == "BackCompat") + + let json = getJSONEncoding(page); // showing on old page + page = fixQuirks(page.ownerDocument); // empty, new page + + // assert(page.ownerDocument.compatMode == "CSS1Compat") + + page.innerHTML = (`<${'pre'}>`); + page.querySelector("pre").textContent = json; + + let error = null; + let processor = new WatchlistProcessor(page); + try { + var data = JSON.parse(json, $revive); + } catch (ex) { + logDiagnostic("file contains invalid JSON: " + name); + error = ex; + } + + function $revive(key, value, ...rest) { + try { + processor.observe(key, value, ...rest); + } catch (ex) { + logDiagnostic("process failed during observation: " + name); + error = error || ex; // keep the first error (if we saw one) + } + return value; + } + + + + ;;; finished_parsing: ;;; + + if (error) throw error; + + + + ;;; parse_succeeded: ;;; + + processor.render(json, data, page.querySelector("pre").firstChild); +} +// diff --git a/demo/watchlist.json.html b/demo/watchlist.json.html new file mode 100644 index 0000000..3bca30c --- /dev/null +++ b/demo/watchlist.json.html @@ -0,0 +1,50 @@ +{ + "@render": "", + "name": "Alice's watchlist", + "url": "https://alice.example.net/movies", + "already_watched": [ + { + "title": "The Iron Giant", "id": "[wikidata:Q867283]", + "score": ":thumbsup:" + }, + { + "title": "Under the Skin", "id": "[wikidata:Q4366287]", + "score": ":thumbsdown:" + }, + { + "title": "The Long Kiss Goodnight", "id": "[wikidata:Q1168399]", + "score": ":thumbsup:" + } + ], + "up_next": [ + {"title": "In the Heat of the Night", "id":"[wikidata:Q622240]"}, + {"title": "Thelma \u0026 Louise", "id":"[wikidata:Q658041]"}, + {"title": "Patriot Games", "id":"[wikidata:Q855222]"}, + {"title": "Crash", "id":"[wikidata:Q188000]"}, + {"title": "The Pope of Greenwich Village", "id":"[wikidata:Q583221]"}, + {"title": "The Lost Bus", "id":"[wikidata:Q125153299]"}, + {"title": "Quisling: The Final Days", "id":"[wikidata:Q125417255]"}, + {"title": "The Railway Man", "id":"[wikidata:Q4178880]"}, + {"title": "Speed", "id":"[wikidata:Q108006]"}, + {"title": "Blue Velvet", "id":"[wikidata:Q660950]"}, + {"title": "Eyes Wide Shut", "id":"[wikidata:Q209481]"}, + {"title": "Casablanca", "id":"[wikidata:Q132689]"}, + {"title": "It Happened One Night", "id":"[wikidata:Q208632]"}, + {"title": "Incendies", "id":"[wikidata:Q1212650]"}, + {"title": "Roofman", "id":"[wikidata:Q130742503]"}, + {"title": "Point Break", "id":"[wikidata:Q1146552]"}, + {"title": "Dead End", "id":"[wikidata:Q676341]"}, + {"title": "Sophie's Choice", "id":"[wikidata:Q165627]"}, + {"title": "A Quiet Place Part II", "id":"[wikidata:Q53911403]"}, + {"title": "The Silence of the Lambs", "id":"[wikidata:Q133654]"}, + {"title": "Maria Bamford: Local Act", "id":"[tmdb:movie/1215158]"}, + {"title": "Mr. Jones", "id":"[wikidata:Q30963297]"}, + {"title": "First Cow", "id":"[wikidata:Q65082453]"}, + {"title": "Fried Green Tomatoes", "id":"[wikidata:Q118375]"}, + {"title": "Glass Onion: A Knives Out Mystery", "id":"[wikidata:Q84712797]"}, + {"title": "Wake Up Dead Man", "id":"[wikidata:Q115931717]"}, + {"title": "Dead Poets Society", "id":"[wikidata:Q106316]"}, + {"title": "The English Patient", "id":"[wikidata:Q63026]"}, + {"title": "Atonement", "id":"[wikidata:Q1626186]"} + ] +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..4ef07e9 --- /dev/null +++ b/index.html @@ -0,0 +1,190 @@ + + + + + + + About @render for JSON + + + + + + + + + + + + +
+ +

About @render for JSON

+ +

+ You can use @render in your JSON to provide rich controls to help with + viewing, filtering, and editing what would otherwise be inert data. +

+ +

+ The @render trick depends on well-defined behavior implemented by all + browsers. +

+ +

+ Read on to understand @render and how it works. +

+
+ +
+ +

1. What does @render look +like?

+ +

In a text editor, it looks like an @render attribute annotating +your JSON. This references your desired loader to bootstrap the UI builder +(or "hydration" logic) for working with the JSON data. For example: + +

+
+    {
+      "@render": "<script src='https://cdn.example/watchlib.js'>",
+      "name": "Alice's watchlist",
+      "url": "https://alice.example.net/movies",
+      "already_watched": [
+        {
+          "title": "The Iron Giant", "id": "[wikidata:Q867283]",
+          "score": ":thumbsup:"
+        },
+        {
+          "title": "Under the Skin", "id": "[wikidata:Q4366287]",
+          "score": ":thumbsdown:"
+        },
+        /* ... */
+    
+ +

In a browser, it looks however you want it to look—subject to what your +@render loader chooses to build and put on the screen. + +

Here's a live example of Alice's watchlist data, showing how the +post-render data looks in the browser:

+ + + +

(You can also follow a direct link to +the JSON payload to see it outside the iframe shown here.)

+ + +

2. How does it work?

+ +

To a JSON parser, @render is just an ordinary property whose value is a string +(albeit one that we know happens to look like HTML). + +

To an HTML parser, your JSON just looks like a bunch of text surrounding a +bit of markup describing a lone script element near the beginning of the file +(in the body rather than the head, but that's no big deal). + +

The trick is to convince browsers to treat the JSON as HTML, whether by +sending an HTML media type from the server, or by using an HTML file extension +(instead of .json) for local files opened from your computer. + +

In an HTML parsing context, the browser sees the markup for your loader +script and then loads and executes the script source. The script, if +self-aware enough, can read out the JSON data and then attach purpose-built, +in-browser controls for viewing, editing, and otherwise working with the +specific type of data contained within the JSON payload (or other data that it +references, or anything else that you want to put on the screen). + + +

3. Why would you want this?

+ +

JSON is an acceptable format for authoring and data exchange, but sometimes +big blobs of JSON can get unwieldy. Not all user agents have perfect (or your +preferred) JSON viewing tools, and even browsers that have tools for +dealing with JSON can't have application-specific affordances for all the +different shapes of JSON they might be absked to show. + +

The @render trick lets you put your data on the screen in the way that you +prefer to show it while remaining valid JSON. + +

And of course, the latitude that this gives you means you can use @render +to make the actual editing experience for JSON-based formats nicer, too. + + +

4. Shouldn't the apps we're +building already provide those editing affordances?

+ +

Maybe. Using @render can be useful for providing a lower-level interface to +the data encoded in the file that ordinary users might not be interested in +(or ever even see). + + +

5. What else?

+ +

There are some caveats.

+ +

In addition to getting it to load in a parsing context that's expecting +markup, you'll need to make sure to properly escape your JSON content for +HTML. Doing this successfully isn't super-complicated. It's enough to escape +all occurrences of U+0026 AMPERSAND and U+003C LESS-THAN +SIGN using the Unicode escape sequence notation for string literals in +JSON, i.e., \u0026 and \u003c, respectively.

+ +

There are additional authoring notes and tips relevant to @render collected on +a separate page.

+ + +
+ + + + + + diff --git a/latest/index.html b/latest/index.html new file mode 100644 index 0000000..1c9ad99 --- /dev/null +++ b/latest/index.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/notes/1/index.html b/notes/1/index.html new file mode 100644 index 0000000..f22cca5 --- /dev/null +++ b/notes/1/index.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/notes/index.html b/notes/index.html new file mode 100644 index 0000000..955f9e8 --- /dev/null +++ b/notes/index.html @@ -0,0 +1,122 @@ + + + + + + + Authoring notes (using @render with JSON) + + + + + + + + + + + + +
+ +

Authoring notes

+ +

Is there anything else to be mindful of with the use of +@render? Yes. There +are a few things—

+ +

To re-iterate the most important: forcing JSON into an HTML parsing context +means that any text that's not escaped for HTML poses a risk to whether and +how the loader does its job. In the worst cases, it could lead to +cross-site scripting if you act without care. It's important to escape the +data to prevent this. A good rule of thumb is to treat any open +angle bracket in your payload (after the @render line, that is) as an +oversight that needs immediate correction, regardless of the provenance of the +data.

+ +

While consistently escaping U+003C LESS-THAN-SIGN (as +\u003c) in all other parts of the JSON payload is enough to +eliminate the cross-site scripting threat, for good measure, you may wish to +always escape U+0026 AMPERSAND (as \u0026), too, +since if the input goes through multiple rounds of parsing, then the sequence +&lt;script can become <script if you are +inconsistent.

+ +

Secondly, if you don't have enough influence over the server configuration +to control the Content-Type header (example: you have a static site), then +you'll probably want to save your data with a .json.html file extension. This +is also/already more or less required for any files that you intend to share +that are "unhosted" and expected to be opened straight from the file +system—it's what will get the correct double-click behavior in most system +file managers and hint to the browser how you want it to be parsed.

+ +

Additionally, when your loader runs, be aware that browsers will by default +put any UI elements that you add to the page into a document in quirks mode +rather than standards mode. This can affect layout and cause +hard-to-track-down issues. It's not impossible to address, but your +application logic does have to be aware of it if you hope to be able get out +of quirks mode.

+ +

Lastly, be aware that, although @render heretofore has had no particular +significance in JSON, we do want to play well with the JSON-LD ecosystem. +It's anticipated that it will be useful to define semantics for the fragment +in the script element src attribute. (It may be beneficial for the fragment +to serve as shorthand for a @type annotation, for example.) For that reason, +use of the fragment identifier in the script element src attribute should be +avoided for now until there has been an opportunity work out the exact +semantics.

+ +

Remember that wherever feasible, you should probably try to use existing +standards like JSON Schema and JSON-LD attributes to describe the data, but +this is not necessary for @render to work.

+ +
+ + + + + +