Generating Shareable Saved Views for a Static Dashboard

Part of the Dashboard State & URL Sharing guide.

Operative rule: name a saved view by the hash of its own content — it is then immutable, infinitely cacheable, and identical for two readers who saved the same thing.

When a URL Stops Being Enough

Self-contained links are better than saved views in every way except one: length. They need no fetch, they can be read by a human, they cannot 404, and they work offline. A saved view gives up all of that, so it should only be reached for when the alternative is a link that breaks.

The threshold in practice is somewhere around three hundred characters. Below it, links survive being pasted into a chat message, an email body or a ticket description. Above it, clients begin wrapping and truncating in ways that produce a link which looks fine to the sender and fails for the recipient — the worst failure mode available, because nobody can see it.

Self-contained link versus saved view A self-contained link carries all state, is readable, works offline and can never fail to resolve, but grows with the state. A saved view is short and fixed in length, but is opaque, requires a fetch and can fail to resolve if the object is removed. Prefer self-contained — until length forces the trade self-contained link readable · no fetch · cannot 404 works offline and in an archive grows with the state — the only flaw use up to ≈ 300 characters saved view fixed length whatever the state holds opaque · needs a fetch · can 404 immutable and cacheable forever use beyond that, or for named references Keep the camera in the fragment even when the rest moves to a saved view — a broken key then still opens the right place.

Production-Ready Implementation

A saved view is a JSON object whose filename is the hash of its own bytes. Writing it needs nothing more than the credentials the build already has for publishing tiles.

from __future__ import annotations

import hashlib
import json
from pathlib import Path
from typing import Any

SAVED_VIEW_VERSION = 1


def canonical_bytes(state: dict[str, Any]) -> bytes:
    """Stable serialisation: sorted keys, no incidental whitespace.

    Two readers who saved the same view must produce identical bytes, or the
    content hash stops deduplicating and the storage fills with near-copies.
    """
    payload = {"v": SAVED_VIEW_VERSION, "state": state}
    return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")


def saved_view_key(state: dict[str, Any]) -> str:
    """Short, collision-resistant, URL-safe key."""
    return hashlib.sha256(canonical_bytes(state)).hexdigest()[:12]


def write_saved_view(root: Path, state: dict[str, Any]) -> str:
    key = saved_view_key(state)
    target = root / "views" / f"{key}.json"
    if target.exists():
        return key                      # identical view already stored
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_bytes(canonical_bytes(state))
    return key


def read_saved_view(root: Path, key: str) -> dict[str, Any] | None:
    if not key.isalnum() or len(key) != 12:
        return None                     # reject anything that is not a key
    path = root / "views" / f"{key}.json"
    if not path.exists():
        return None
    payload = json.loads(path.read_text(encoding="utf-8"))
    if payload.get("v") != SAVED_VIEW_VERSION:
        return None                     # migrate explicitly, never guess
    return payload.get("state")

The key-format check in read_saved_view matters more than it looks. The key arrives from a URL, and it is used to build a path — validating its shape before touching the filesystem is what stops a crafted key from reaching anywhere it should not.

The Read Path in the Browser

Loading a saved view without a blank first frame The page reads the key and the inline camera from the fragment. The camera is applied immediately so the map opens in the right place. The saved view is fetched in parallel and, when it arrives, the layer and filter state is applied. If the fetch fails the map remains usable at the correct location. Camera first, everything else when it arrives read fragment #map=…&v=a91c4f2b construct with the camera right place, first frame fetch views/<key> immutable, cached apply rest layers, filters Failure behaviour, decided in advance ◦ fetch 404s → keep the camera, show a quiet notice, leave layers at their defaults ◦ version mismatch → same treatment; never partially apply an unknown schema ◦ the map must always be usable, even when the saved state is not

Retention, Deduplication and Cost

Because the key is a content hash, two readers saving identical views write the same object once. That property does most of the housekeeping for free: a busy dashboard accumulates far fewer objects than it has share events, and re-sharing an existing view costs nothing at all.

What remains is retention. Views created ad hoc during a working week rarely need to outlive the quarter; views referenced from documentation need to last as long as the documentation does. The simplest policy that works is two prefixes — views/tmp/ with a lifecycle rule, and views/keep/ without one — and a share control that writes to the second only when the reader explicitly asks for a permanent link.

Two retention classes for saved views A temporary prefix holds ad-hoc shares and expires them after about ninety days under a storage lifecycle rule. A permanent prefix holds views referenced from documentation, runbooks and tickets and is never expired. The share control decides which prefix to write based on an explicit reader choice. Two prefixes, one lifecycle rule, no database views/tmp/ — expires after ~90 days ad-hoc shares in chat "look at this" during an incident the default for the share button views/keep/ — never expired links in documentation and runbooks references in tickets and reports written only on an explicit choice Content hashing means re-sharing an identical view is free — the object already exists and the key is the same. Promoting a temporary view to permanent is a copy, not a re-save: the key, and therefore every existing link, stays valid.

Verification Steps

  • Save the same view twice and confirm one object exists and both keys match.
  • Request a key that does not exist and confirm the map opens at the fragment’s camera with a quiet notice.
  • Confirm saved-view objects are served with a long cache lifetime — they are immutable by construction.
  • Confirm the share control writes to the temporary prefix unless the reader asked for a permanent link.
  • Confirm a key with unexpected characters is rejected before any storage lookup.

Common Errors & Fixes

Two identical views produce different keys

Serialisation is not canonical — key order or whitespace is varying. Sort keys and use compact separators, as canonical_bytes does.

Saved views work in development and 404 in production

The build writes them under a path the deployment does not publish. Write them into the same output directory the tiles go to, so one publish step covers both.

A lifecycle rule expired objects under the permanent prefix, or a permanent link was written to the temporary one. Audit which prefix the share control writes to, and exclude the permanent prefix from every lifecycle rule explicitly.

The saved view opens the right layers but the wrong place

The camera was moved into the saved object and taken out of the fragment. Keep it in both: the fragment is what makes a failed fetch survivable.

Writing a Saved View Without a Backend

The obvious objection to storing saved views in object storage is that a static page cannot write to it. That is true of the storage’s normal credentials and not true of the mechanisms designed for exactly this case.

The most common arrangement is a presigned upload: a small endpoint — an edge function, a serverless handler, a single route in whatever already authenticates the dashboard — issues a short-lived signed URL that permits a single PUT to one key under the views prefix. The page computes the content hash, requests a signature for that key, uploads and is done. The endpoint stays tiny because it makes no decisions about content; it only decides whether this reader may save a view at all.

Two constraints keep it safe. Restrict the signature to the exact key the hash produces, so a client cannot write anywhere else under the prefix. And cap the object size, because a saved view is a few kilobytes and anything larger is either a bug or an attempt to use the bucket as free storage.

If even that endpoint is unwelcome, there is a lower-tech option: the dashboard can offer a copyable blob of state that a reader pastes into a ticket, and a small internal tool writes into the views prefix when a permanent link is genuinely needed. It is clumsier, but it keeps the deployment entirely static and covers the case where saved views are an occasional need rather than a daily one.

What is not viable is writing from the client with long-lived credentials embedded in the page. Anything in the bundle is public, and a bucket writable by everyone who has loaded the dashboard will be found and used for something else.

Naming, Listing and Cleaning Up

Because keys are content hashes, they carry no meaning to a reader. That is fine for links pasted into a conversation, where the surrounding text supplies the context, and less fine for a set of standard views a team returns to weekly. For those, a small hand-maintained index — a JSON file mapping human names to keys, published with the site — turns opaque keys into a menu without introducing a database.

The index also solves retention. A view referenced from the index is one the cleanup process must never expire, which makes the rule mechanical rather than a matter of judgement: everything in the index is permanent, everything else follows the lifecycle policy on the temporary prefix.