DEV Community

CrisisCore-Systems
CrisisCore-Systems

Posted on • Edited on • Originally published at dev.to

Exports as a security boundary from local record to shareable report

Part 6 of Protective Computing in Practice — Start here: https://dev.to/crisiscoresystems/offline-first-without-a-backend-a-local-first-pwa-architecture-you-can-trust-3j15

Series: Start here · Part 1 · Part 2 · Part 3 · Part 4 · Part 5 · Part 6 · Part 7 · Part 8 · Part 9 · Part 10

This post is Part 6 in a Dev.to series grounded in the open-source Pain Tracker repo.

  • Not medical advice.
  • Not a compliance claim.
  • This post is about trust boundaries, not “features.”

If you want privacy-first, offline health tech to exist without surveillance funding it: sponsor the build → https://paintracker.ca/sponsor

If you haven’t read Part 5 yet:


Exports are where the trust model changes

In a local-first app, the default promise is usually:

your data stays on your device

Exports are the moment that promise becomes conditional.

Because as soon as you create a file:

  • it can be emailed
  • it can be uploaded to cloud backups
  • it can be forwarded
  • it can be printed
  • it can sit in Downloads forever

That’s not a reason to avoid exports.

It’s a reason to treat export like a deliberate boundary in both code and UX.


Pain Tracker’s export stance: explicit, local, and user-triggered

The core export utilities live here:

  • src/utils/pain-tracker/export.ts

The UI that invokes them (with filters) is here:

  • src/components/export/DataExportModal.tsx

The pattern is intentionally boring:

1) user chooses a format (CSV/JSON/PDF)
2) user optionally filters the date range / symptoms / locations
3) app generates a string (or PDF data URI)
4) app downloads it via a normal browser download

No background exporting, no scheduled exports, no “send to provider” button that quietly turns into a network feature.


The real boundary is “create a file”

Pain Tracker downloads data using a small helper:

  • downloadData(data, filename, mimeType) in src/utils/pain-tracker/export.ts

It creates a Blob, then triggers a download by clicking an <a> element programmatically.

That’s important because it’s a clear, user-observable browser action:

  • you can see the file land
  • you can delete it
  • you can decide where it goes

It’s a simple boundary you can explain to a tired user.


Exports are not “safe” by default (and shouldn’t pretend to be)

The CSV and JSON exports can include notes, and notes are the highest-risk field in most journaling apps.

You can see that directly in the implementation:

  • CSV includes a Notes column and escapes quotes
  • JSON is literally JSON.stringify(entries, null, 2)

This is good honesty:

  • the export is a faithful copy
  • the app isn’t claiming it can “anonymize” your narrative

If you need de-identification, that’s a different feature with a different risk profile.


Treat “tracking exports” as a separate, minimal channel

Even with no backend, teams often want to answer questions like:

  • do people use exports?
  • which formats matter?

Pain Tracker uses two kinds of tracking around exports:

1) Local-only usage tracking

  • trackExport(type, recordCount) in src/utils/usage-tracking.ts
  • it stores the last ~100 export events in localStorage
  • it stores counts, not content

It also sanitizes metadata so Class A fields aren’t stored in plaintext localStorage.

2) Optional GA4 events

  • export utilities call trackDataExported(format, entryCount)
  • those events are gated behind env + explicit consent (covered in Part 8)

Part 8 walks through the full gating model behind those events: Analytics without surveillance: explicit consent, layered gates, and never sending Class A data.

The key point is what’s not tracked:

  • not the file contents
  • not notes
  • not the “what did you export” payload

UX: don’t make export feel like a trap

In Pain Tracker, export is presented as:

  • an explicit action in an export modal
  • a user-visible download

Good export UX in sensitive apps is mostly about preventing regret:

  • clear format labels (CSV vs JSON vs PDF)
  • clear file naming
  • an obvious success state
  • no surprise side effects

If you add “share” features later, treat them as new boundaries: they turn a local file into network exposure.

For a concrete paperwork-oriented example built on top of this boundary, see How Pain Tracker Pro Streamlines WorkSafeBC Claims: A Composite Case Study.


Next up

Part 7 covers WorkSafeBC-oriented workflows — and how to keep language careful and grounded in what the repo actually does.

Prev: Part 5 — Trauma-informed UX + accessibility as architecture
Next: Part 7 — WorkSafeBC-oriented workflows (careful language)

Next up: https://dev.to/crisiscoresystems/worksafebc-oriented-workflows-without-overclaims-structured-summaries-careful-language-2n3i


Support this work

Top comments (0)