Hosted onseed.hyper.mediavia theHypermedia Protocol

[DONE] Private visibility is not preserved when desktop publish uses daemon createDocumentChange

Yes — I researched it.

What I found

There is a real code smell/bug candidate:

  • frontend/apps/desktop/src/models/documents.ts computes visibility = ResourceVisibility.PRIVATE for a first publish of a private draft.

  • But if the publish goes through the daemon path, frontend/apps/desktop/src/utils/publish-document.ts rewrites it to:

visibility: ResourceVisibility.UNSPECIFIED

in createDocumentChangeRequest(...).

So the daemon request drops the caller’s visibility intent.

Why this matters

On the backend:

  • backend/api/documents/v3alpha/documents.go

treats visibility like this:

  • PRIVATE → make ref private

  • PUBLIC → make ref public

  • UNSPECIFIED → keep existing visibility for updates, default to public for new docs

So if a new private document were published through the daemon path with UNSPECIFIED, it could become public.

But here’s the important nuance

This is probably not the main explanation for “first publish of a private document doesn’t behave private.”

Why:

Non-home first publish does not use the daemon path

shouldUseDaemonCreateDocumentChange(...) uses daemon only when:

  • !!baseVersion && !!genesis

  • or !path (root/home doc)

Private drafts are created with a non-empty random path, so their first publish usually goes through the generic client path, not daemon.

And the generic path does preserve private visibility:

  • frontend/packages/shared/src/create-web-universal-client.ts

  • frontend/packages/client/src/change.ts

  • frontend/packages/client/src/change.test.ts

There is even a test confirming:

  • signDocumentChange(... visibility: 2 ...)

  • includes "visibility": "Private" in the Ref blob.

So what is the daemon bug actually affecting?

Likely only narrower cases:

  1. Updates / re-publishes of existing docs that use daemon

  2. Home/root docs, which also use daemon

But:

  • updates with UNSPECIFIED keep existing visibility on the server, so that is often harmless

  • root docs cannot be private anyway (documents.go rejects private root docs)

So the daemon bug looks real, but also lower impact than it first appears.

Strong evidence it is already known / accidental

There are signs the team already suspected a visibility issue:

  • frontend/apps/desktop/src/models/documents.ts has:

    • // TODO(temp): remove after verifying private document visibility fix

    • logging of requestedVisibility and returnedVisibility

Also, the current unit test codifies the problematic behavior:

  • frontend/apps/desktop/src/utils/__tests__/publish-document.test.ts

expects createDocumentChangeRequest(...PRIVATE...) to still produce:

ResourceVisibility.UNSPECIFIED

So the bad behavior is currently baked into tests.

My conclusion

1. Yes, the daemon-path visibility drop is real

publish-document.ts discards requested visibility.

2. But it likely does not explain your main symptom for first-published private docs

Because those usually go through the non-daemon path, which preserves privacy.

3. More likely explanations for your observed behavior are:

  • you are testing as the owner, who is allowed to see private docs

  • UI surfaces intentionally include private docs for writers

    • useCanSeePrivateDocs(...) returns true for writer capability on the site home

  • the environment you tested may not be enforcing PublicOnly

  • or there is a separate privacy bug elsewhere

Best short takeaway

The daemon-path visibility bug is real, but probably not the primary cause of “private docs don’t behave private on first publish.”

Recap

The desktop daemon publish path drops requested private visibility by forcing ResourceVisibility.UNSPECIFIED in frontend/apps/desktop/src/utils/publish-document.ts, and the backend treats unspecified as “preserve on updates, default public for new docs.” However, first publish of non-home private docs usually bypasses daemon and uses the generic client signing path, which correctly preserves "Private" visibility in the Ref blob.

  • The daemon issue is real and currently even reflected in tests.

  • It is probably lower-impact than it first looks because the affected daemon flows are mainly updates and root docs.

  • Your observed “not private” behavior is more likely due to owner/writer access, environment config (PublicOnly), or another privacy bug outside this daemon path.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime