home.social

#localfirst — Public Fediverse posts

Live and recent posts from across the Fediverse tagged #localfirst, aggregated by home.social.

  1. I needed a bar chart from a client spreadsheet. Names, invoice totals, a few formulas. Every “paste your CSV” tool I opened wanted an account, a POST, or both. The data wasn’t secret in a compliance sense — it just wasn’t something I wanted sitting in someone else’s temp bucket for a week.

    So I built the pipeline in the tab: parse → choose encodings → render → export. No upload step. The file never leaves the machine.

    This isn’t a product pitch. It’s the shape of a local-first chart path that frontend folks keep reinventing, and the places it usually breaks.

    What “local-first” means for charts


    Local-first here is boring and specific:

    1. Parse in the browser — FileReader / drag-drop / paste from Sheets
    2. Infer or pick columns — which field is the category, which is the measure
    3. Render with a client library — ECharts, Chart.js, Observable Plot, whatever you already ship
    4. Export as a blob — PNG / SVG / CSV download via URL.createObjectURL

    Nothing in that list needs a chart SaaS. The temptation is to outsource step 2–4 because “AI will pick the right chart type.” You can still do that locally (WebLLM, Ollama on localhost, or a heuristic). The point is the CSV bytes don’t have to cross a trust boundary to get a PNG.

    Where paste-and-upload usually goes wrong


    I’ve used a lot of hosted plotters. The friction isn’t the UI — it’s the data path:

    • Silent retention — “we delete in 24h” is a promise, not a guarantee you can verify
    • Account gates — free tier wants email before you see a download button
    • Column renaming — the service “helps” by rewriting Invoice Total ($) into value, then your export no longer matches the source sheet
    • One more network hop — a flaky Wi‑Fi day turns a five-minute chart into a support ticket

    For internal ops sheets and freelance client data, that path is the wrong default. The silicon is already on the desk.

    A minimal pipeline that stays in the tab


    Here’s the skeleton I keep coming back to.

    1. Ingest without a server

    async function readTable(file) {
    const text = await file.text();
    // or Papa Parse / d3-dsv for quoted fields and European decimals
    return parseCsv(text);
    }

    Paste from Sheets is often more useful than file pick: listen for paste, read clipboardData.getData("text/plain"), split on tabs.

    2. Separate “table” from “chart spec”

    Don’t bake chart type into the parser. Keep a plain array of row objects, then a small spec:

    type ChartSpec = {
    type: "bar" | "line" | "pie";
    x: string; // column name
    y: string; // column name
    title?: string;
    };

    If you later add an agent to suggest type / x / y, feed it column names and sample rows — not the whole file to a remote API unless the user opted in.

    3. Validate the spec against real columns

    This is the bug that burns people. Models invent a column called value. Heuristics pick the first numeric field even when it’s an ID. Validate:

    function assertSpec(spec, columns) {
    if (!columns.includes(spec.x) || !columns.includes(spec.y)) {
    throw new Error("spec points at missing columns");
    }
    }

    Fail loud. Fall back to a deterministic guess only after the fancy path fails.

    4. Render and export as blobs

    ECharts (and most canvas/SVG libs) can getDataURL or serialize SVG. Wrap that in a download:

    function downloadPng(dataUrl, name = "chart.png") {
    const a = document.createElement("a");
    a.href = dataUrl;
    a.download = name;
    a.click();
    }

    No FormData to /api/export. No “email me the PNG.”

    Optional AI without giving up local


    If you want “make this a pie” or “title it June collected,” you have three honest options:

    ApproachData leaves machine?Good forHeuristic (column types + cardinality)NoDefault first chartLocal model (WebLLM / Ollama)No*Spec suggestionsHosted LLMYes, unless you redactOnly with explicit opt-in


    *First weight download or 127.0.0.1 still isn’t “upload the CSV to a chart vendor.”

    I treat AI as a spec suggester, not a renderer. The table and the canvas stay under your code. The model returns JSON; you validate; you draw. If validation fails three times, ship the heuristic chart anyway — a wrong-but-local bar chart beats a spinner waiting on a remote plot API.

    DX checklist before you reach for SaaS


    When I’m tempted to paste into a hosted tool, I ask:

    1. Does this sheet contain names, amounts, or anything I wouldn’t put in a public Gist?
    2. Do I already have a chart library in the app or a side panel?
    3. Is the job “one PNG for a slide” or “ongoing dashboard with auth”?
    4. Can export be a blob download in under fifty lines?

    If (1) is yes and (3) is a one-off, local wins. Hosted dashboards still make sense for multi-user live data. One-shot client charts usually don’t need that architecture.

    What I’d ship in a weekend spike


    • Drag-drop + paste ingest
    • Column picker (manual) with a heuristic default
    • One chart type done well (bar), then line/pie
    • PNG + SVG export
    • A toggle: “suggest encodings with local model” — off by default

    That’s enough to stop uploading spreadsheets for a title slide. You can grow it later with saved specs in localStorage, themes, or an agent that only sees column headers.

    The pattern matters more than any one library: keep the bytes on the device until the user explicitly chooses otherwise. Charts are a perfect fit for that rule — the input is a file you already have, and the output is a file you want back.

    Next time a tool asks you to create an account before it will draw bars from your own CSV, that’s a signal. The pipeline above fits in a single page. Build that page once, and the upload prompt gets a lot easier to ignore.#frontend #localfirst #dx #webdev #software #coding #development #engineering #inclusive #community
    Local-First Chart Pipeline: CSV Chart Export Without Third-Party Upload

  2. The AI hackopalypse is a big opportunity for #localFirst software. Software running in sandboxes on your machine that is not exposed to the internet is much harder to attack.

  3. A cloud architecture lesson from Billnix: don't collapse sync, backup and export into one vague “cloud” feature.

    • Drift ↔ Rust/PostgreSQL handles normal same-account continuity.
    • Google Drive checkpoints provide a separate optional recovery path.
    • Drive/export copies are currently documented as readable rather than zero-knowledge encrypted objects.

    Different failure modes, different privacy boundaries.

    #DataArchitecture #Backup #Billnix #LocalFirst

  4. How do you sync data between your own devices, using the cloud, but retain data sovereignty?

    I think I have a solution with Hammer's built-in syncing protocol.

    hammer.ink/blog/principles/syn

    #amWriting #privacy #opensource #localfirst #digitalsovereignty

  5. @grooveplayer

    WARNING, if you use this player, don't update! Think first.

    Your #localfirst player will become public - no way to stop, nor delete the upload to iCloud.

    #Groove #GrooveMusicPlayer #iOS #iPhone #Music #Apps #AppStore #iCloud #Privacy

Share on Mastodon

Enter the server where you have an account.