Skip to content

feat: report and resolve .env differences one variable at a time - #7

Open
ilmakio wants to merge 5 commits into
feat/env-layout-preservationfrom
feat/env-per-entry-sync
Open

ilmakio wants to merge 5 commits into
feat/env-layout-preservationfrom
feat/env-per-entry-sync

Conversation

@ilmakio

@ilmakio ilmakio commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Stacked on #6 (which is stacked on #5) — this PR's diff is against feat/env-layout-preservation.

Why

The linked-file status could only say that the file as a whole had changed, and both ways out of it replaced everything in one direction. When one variable moved on disk that is the wrong size of decision, so the actual way to apply it was to copy the value across by hand — which is the work linking a file was supposed to remove.

How

The link now records the digest of each variable's value at every sync. That is what makes it possible to say which side moved for a given variable, instead of only for the file:

  • fileChanged — the file's value moved, the item's did not
  • vaultChanged — the item's moved, the file's did not
  • diverged — both moved, or the link predates this and the answer is genuinely unknown
  • onlyInVault / onlyInFile

The detail pane marks the entry that differs and offers Use file value / Keep this value. Both apply to that one variable:

  • Pulling one variable writes nothing to the file and leaves every other field alone. A variable the file has and the item does not arrives with the same sensitivity treatment an import would give it.
  • Pushing one variable reads the file, merges only that assignment, and uses the text it just read as the write precondition — so every other line stays, comments included, and a change arriving in between fails the write instead of being overwritten.
  • Variables the file sets and the item does not have are listed in the linked-file section with Add to item, since they have no field of their own to be marked against.

The whole-file digests are accepted as seen only when nothing differs any more. Recording the file as synced after a partial pull would quietly swallow the changes nobody has looked at yet, so until then the file-level status keeps reporting that both sides hold something the other does not — which is true.

A variable emptied in the vault but still set in the file now stays visible in the detail pane. It used to be hidden as an empty value, which left the difference with nothing to click.

Security

The cached per-variable states carry no values — only which side moved — so unlocking and browsing does not leave a cache of every linked file's secrets in the view model. The value is read from the file at the moment it is applied. The cache is cleared on lock with the rest of the derived state, and there is a test for that.

The per-variable baseline is a SHA-256 of each value, stored inside the encrypted vault, the same shape as the digests the dismissed-health-finding records already keep.

Compatibility

syncedFieldDigests is a new optional field on the link, decoded with decodeIfPresent. A link made by an earlier version has no baseline, so a variable that differs is reported as diverged — no invented direction — and the next sync in either direction records one.

Tests

New EnvFieldDriftTests / EnvFieldSyncTests: attribution of each side's move (and of a repeated variable, resolved last-wins as dotenv readers do); no baseline meaning unattributed rather than guessed; matching values never reported; pulling one variable leaving the others, the file and the whole-file baseline alone while keeping the replaced value in history; pushing one variable keeping a comment and an untracked variable added on disk; resolving the last difference marking the item in sync; adding a variable that exists on only one side, in each direction; and locking clearing the cache.

The linked-file status could only say that the file as a whole had changed,
and the only ways out replaced everything in one direction. When a single
variable moved on disk that was the wrong size of decision, so the way to
apply it was to copy the value across by hand.

The link now records the digest of each variable's value at every sync, which
is what makes it possible to say which side moved for a given variable rather
than only for the file. The detail pane marks the entry that differs, and
offers to take the file's value or to write its own — both of which touch
that one variable and nothing else. A variable the file sets and the item does
not is listed in the linked-file section, where it can be added.

- Pulling one variable writes nothing, and leaves every other field alone.
- Pushing one variable merges into the file as it is now, with that text as
  the write precondition, so every other line — comments included — stays.
- The whole-file digests are only accepted as seen once nothing differs.
  Recording the file as synced earlier would swallow the changes the owner
  has not looked at yet.
- A variable emptied in the vault but still set in the file stays visible in
  the detail pane; hiding it left the difference with nothing to click.
- The per-variable states hold no values, only which side moved, and they are
  cleared on lock like everything else derived from vault contents.

Links made by earlier versions have no baseline. A variable that differs is
reported as diverged rather than attributed to a side that cannot be known;
the next sync records one.
An edit landing while the file operation was suspended off-main would have
been recorded as the synced value, which then attributed the item's own change
to the file on the next check.
With a note above every variable, "is this comment about the entry above
or the one below?" is the whole question, and spacing alone leaves it to be
measured by eye.

- A comment block and the variables it introduces are bracketed by a rule
  down the left, so what the note covers is drawn rather than inferred. The
  gutter is always laid out, so variables keep one left edge whether or not
  their group carries a comment.
- Runs of variables are separated by a divider, sections by a wider gap.
- Comments are set in the monospaced style the values use: they are text out
  of the owner's file, not a caption PassStore wrote.
- The per-variable sync badge moved from above the entry to directly under
  the value it is about, and its two choices now line up under the sentence
  they answer instead of under its icon.
A name and the value under it are one thing; the 8pt gap between them was the
same as the one to whatever followed the value, so nothing said which. The
label now sits 4pt above its value, and a note or an unresolved difference
keeps the wider gap that marks it as a separate statement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant