hadsync 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {hadsync-0.2.2 → hadsync-0.2.3}/.gitignore +1 -0
  2. {hadsync-0.2.2 → hadsync-0.2.3}/CHANGELOG.md +10 -0
  3. {hadsync-0.2.2 → hadsync-0.2.3}/PKG-INFO +45 -12
  4. {hadsync-0.2.2 → hadsync-0.2.3}/README.md +44 -11
  5. hadsync-0.2.3/docs/cli-push.jpg +0 -0
  6. hadsync-0.2.3/docs/cli-status.jpg +0 -0
  7. hadsync-0.2.3/docs/cli-validate-all-pass.jpg +0 -0
  8. hadsync-0.2.3/docs/cli-validate-pass.jpg +0 -0
  9. hadsync-0.2.3/docs/cli-validate-warnings.jpg +0 -0
  10. hadsync-0.2.3/hadsync/__init__.py +1 -0
  11. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/cli.py +9 -2
  12. {hadsync-0.2.2 → hadsync-0.2.3}/pyproject.toml +1 -1
  13. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/statusBar.ts +6 -1
  14. hadsync-0.2.2/hadsync/__init__.py +0 -1
  15. {hadsync-0.2.2 → hadsync-0.2.3}/.claude/settings.local.json +0 -0
  16. {hadsync-0.2.2 → hadsync-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {hadsync-0.2.2 → hadsync-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  18. {hadsync-0.2.2 → hadsync-0.2.3}/.hadsync.yaml +0 -0
  19. {hadsync-0.2.2 → hadsync-0.2.3}/CONTRIBUTING.md +0 -0
  20. {hadsync-0.2.2 → hadsync-0.2.3}/LICENSE +0 -0
  21. {hadsync-0.2.2 → hadsync-0.2.3}/docs/diff-conflict-summary.jpg +0 -0
  22. {hadsync-0.2.2 → hadsync-0.2.3}/docs/diff-show-flag.jpg +0 -0
  23. {hadsync-0.2.2 → hadsync-0.2.3}/docs/vscode-autocomplete.jpg +0 -0
  24. {hadsync-0.2.2 → hadsync-0.2.3}/docs/vscode-command-palette.jpg +0 -0
  25. {hadsync-0.2.2 → hadsync-0.2.3}/docs/vscode-entities-screenshot.jpg +0 -0
  26. {hadsync-0.2.2 → hadsync-0.2.3}/docs/vscode-problems-panel.jpg +0 -0
  27. {hadsync-0.2.2 → hadsync-0.2.3}/docs/vscode-screenshot.jpg +0 -0
  28. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/config.py +0 -0
  29. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/converter.py +0 -0
  30. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/entities.py +0 -0
  31. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/ha_rest.py +0 -0
  32. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/ha_ws.py +0 -0
  33. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/output.py +0 -0
  34. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/schema.py +0 -0
  35. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/state.py +0 -0
  36. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/validator.py +0 -0
  37. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync/watcher.py +0 -0
  38. {hadsync-0.2.2 → hadsync-0.2.3}/hadsync_design.docx +0 -0
  39. {hadsync-0.2.2 → hadsync-0.2.3}/tests/__init__.py +0 -0
  40. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/__init__.py +0 -0
  41. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_config.py +0 -0
  42. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_converter.py +0 -0
  43. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_entities.py +0 -0
  44. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_schema.py +0 -0
  45. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_state.py +0 -0
  46. {hadsync-0.2.2 → hadsync-0.2.3}/tests/unit/test_validator.py +0 -0
  47. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/.vscodeignore +0 -0
  48. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/LICENSE +0 -0
  49. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/README.md +0 -0
  50. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/package-lock.json +0 -0
  51. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/package.json +0 -0
  52. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/commands.ts +0 -0
  53. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/completion.ts +0 -0
  54. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/diagnostics.ts +0 -0
  55. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/extension.ts +0 -0
  56. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/src/runner.ts +0 -0
  57. {hadsync-0.2.2 → hadsync-0.2.3}/vscode-hadsync/tsconfig.json +0 -0
@@ -3,6 +3,7 @@
3
3
  uv.lock
4
4
  .python-version
5
5
  __pycache__/
6
+ dist/
6
7
  *.py[cod]
7
8
  *.egg-info/
8
9
  dist/
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.2.3] — 2026-05-11
4
+
5
+ ### Fixed
6
+
7
+ - **False-positive "modified" in `hadsync status` after a clean pull** — macOS APFS can commit a file's mtime a few microseconds after Python records `last_pull`, making `mtime > last_pull` by a sub-second amount even though the pull itself wrote the file. Fixed by comparing timestamps at whole-second granularity: a difference of less than one second means the pull wrote the file, not a user edit. Applied in `hadsync status`, `hadsync diff` conflict detection, and the VS Code status bar.
8
+
9
+ [v0.2.3]: https://github.com/gevgev/hadsync/releases/tag/v0.2.3
10
+
11
+ ---
12
+
3
13
  ## [v0.2.2] — 2026-05-10
4
14
 
5
15
  ### Fixed — VS Code Extension
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hadsync
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Home Assistant Dashboard Sync — pull, edit, and push Lovelace dashboards as code
5
5
  Project-URL: Homepage, https://github.com/gevgev/hadsync
6
6
  Project-URL: Repository, https://github.com/gevgev/hadsync
@@ -93,29 +93,32 @@ HA stores Lovelace dashboard configs in its internal storage layer. There is no
93
93
  ## Installation
94
94
 
95
95
  ```bash
96
- pip install hadsync # once published to PyPI
96
+ pip install hadsync
97
97
  ```
98
98
 
99
99
  Requires Python 3.11+.
100
100
 
101
- ### Install from source
102
-
103
- **With uv — installs `hadsync` globally so it works from any directory:**
101
+ **With uv (recommended — installs globally so `hadsync` works from any directory):**
104
102
 
105
103
  ```bash
106
- # production install (run from anywhere after this)
107
- uv tool install /path/to/hadsync
104
+ uv tool install hadsync
105
+ ```
108
106
 
109
- # editable install code changes take effect immediately, no reinstall needed
110
- uv tool install --editable /path/to/hadsync
107
+ After installation, `hadsync` is available system-wide. Upgrade to a newer release with:
111
108
 
112
- # update after pulling new commits (non-editable)
113
- uv tool install --reinstall /path/to/hadsync
109
+ ```bash
110
+ pip install --upgrade hadsync
111
+ # or
112
+ uv tool upgrade hadsync
114
113
  ```
115
114
 
116
- **With pip:**
115
+ ### Install from source
117
116
 
118
117
  ```bash
118
+ # editable install — code changes take effect immediately
119
+ uv tool install --editable /path/to/hadsync
120
+
121
+ # or with pip
119
122
  pip install -e ".[dev]"
120
123
  ```
121
124
 
@@ -258,6 +261,36 @@ battery-status
258
261
 
259
262
  ## In Action
260
263
 
264
+ ### `hadsync validate` — issues found
265
+
266
+ ![hadsync validate climate-overview showing 2 warnings: unknown entity on line 192 and a card missing its entity field on line 227](docs/cli-validate-warnings.jpg)
267
+
268
+ *Validation catches two problems before anything reaches HA: an entity ID that no longer exists in the registry, and a `sensor` card missing its required `entity` field. Both are reported with exact line numbers.*
269
+
270
+ ### `hadsync validate` — all clear
271
+
272
+ ![hadsync validate climate-overview showing PASS after the issues were fixed](docs/cli-validate-pass.jpg)
273
+
274
+ *After fixing the two issues the dashboard passes cleanly. The same command validates all dashboards at once when run without an ID argument.*
275
+
276
+ ### `hadsync validate` — full suite
277
+
278
+ ![hadsync validate showing all 13 dashboards with PASS](docs/cli-validate-all-pass.jpg)
279
+
280
+ *All 13 storage-mode dashboards pass in a single run — safe to use in CI pipelines since the command exits non-zero on any error.*
281
+
282
+ ### `hadsync status` — sync overview
283
+
284
+ ![hadsync status table showing 13 dashboards with last pull and push timestamps; climate-overview highlighted as modified](docs/cli-status.jpg)
285
+
286
+ *A quick at-a-glance table showing when each dashboard was last pulled, when it was last pushed, and whether the local file has been modified since the pull. `climate-overview` shows as `modified` — a local edit is pending.*
287
+
288
+ ### `hadsync push` — safe confirmation
289
+
290
+ ![hadsync push climate-overview showing the view and card counts for HA vs local before asking for confirmation](docs/cli-push.jpg)
291
+
292
+ *Before pushing, hadsync shows the current HA state alongside what would be sent — 6 views, 32 cards on both sides here — and requires an explicit `y` to proceed. A `--dry-run` flag shows this summary without connecting to HA at all.*
293
+
261
294
  ### `hadsync diff` — conflict summary
262
295
 
263
296
  ![hadsync diff showing a CONFLICT: HA has 6 views 33 cards, local has 6 views 32 cards, both changed since last pull](docs/diff-conflict-summary.jpg)
@@ -36,29 +36,32 @@ HA stores Lovelace dashboard configs in its internal storage layer. There is no
36
36
  ## Installation
37
37
 
38
38
  ```bash
39
- pip install hadsync # once published to PyPI
39
+ pip install hadsync
40
40
  ```
41
41
 
42
42
  Requires Python 3.11+.
43
43
 
44
- ### Install from source
45
-
46
- **With uv — installs `hadsync` globally so it works from any directory:**
44
+ **With uv (recommended — installs globally so `hadsync` works from any directory):**
47
45
 
48
46
  ```bash
49
- # production install (run from anywhere after this)
50
- uv tool install /path/to/hadsync
47
+ uv tool install hadsync
48
+ ```
51
49
 
52
- # editable install code changes take effect immediately, no reinstall needed
53
- uv tool install --editable /path/to/hadsync
50
+ After installation, `hadsync` is available system-wide. Upgrade to a newer release with:
54
51
 
55
- # update after pulling new commits (non-editable)
56
- uv tool install --reinstall /path/to/hadsync
52
+ ```bash
53
+ pip install --upgrade hadsync
54
+ # or
55
+ uv tool upgrade hadsync
57
56
  ```
58
57
 
59
- **With pip:**
58
+ ### Install from source
60
59
 
61
60
  ```bash
61
+ # editable install — code changes take effect immediately
62
+ uv tool install --editable /path/to/hadsync
63
+
64
+ # or with pip
62
65
  pip install -e ".[dev]"
63
66
  ```
64
67
 
@@ -201,6 +204,36 @@ battery-status
201
204
 
202
205
  ## In Action
203
206
 
207
+ ### `hadsync validate` — issues found
208
+
209
+ ![hadsync validate climate-overview showing 2 warnings: unknown entity on line 192 and a card missing its entity field on line 227](docs/cli-validate-warnings.jpg)
210
+
211
+ *Validation catches two problems before anything reaches HA: an entity ID that no longer exists in the registry, and a `sensor` card missing its required `entity` field. Both are reported with exact line numbers.*
212
+
213
+ ### `hadsync validate` — all clear
214
+
215
+ ![hadsync validate climate-overview showing PASS after the issues were fixed](docs/cli-validate-pass.jpg)
216
+
217
+ *After fixing the two issues the dashboard passes cleanly. The same command validates all dashboards at once when run without an ID argument.*
218
+
219
+ ### `hadsync validate` — full suite
220
+
221
+ ![hadsync validate showing all 13 dashboards with PASS](docs/cli-validate-all-pass.jpg)
222
+
223
+ *All 13 storage-mode dashboards pass in a single run — safe to use in CI pipelines since the command exits non-zero on any error.*
224
+
225
+ ### `hadsync status` — sync overview
226
+
227
+ ![hadsync status table showing 13 dashboards with last pull and push timestamps; climate-overview highlighted as modified](docs/cli-status.jpg)
228
+
229
+ *A quick at-a-glance table showing when each dashboard was last pulled, when it was last pushed, and whether the local file has been modified since the pull. `climate-overview` shows as `modified` — a local edit is pending.*
230
+
231
+ ### `hadsync push` — safe confirmation
232
+
233
+ ![hadsync push climate-overview showing the view and card counts for HA vs local before asking for confirmation](docs/cli-push.jpg)
234
+
235
+ *Before pushing, hadsync shows the current HA state alongside what would be sent — 6 views, 32 cards on both sides here — and requires an explicit `y` to proceed. A `--dry-run` flag shows this summary without connecting to HA at all.*
236
+
204
237
  ### `hadsync diff` — conflict summary
205
238
 
206
239
  ![hadsync diff showing a CONFLICT: HA has 6 views 33 cards, local has 6 views 32 cards, both changed since last pull](docs/diff-conflict-summary.jpg)
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -578,7 +578,9 @@ async def _diff_async(dashboard_id: Optional[str], show: bool) -> None:
578
578
  mtime = datetime.fromtimestamp(
579
579
  yaml_path.stat().st_mtime, tz=timezone.utc
580
580
  )
581
- local_modified = mtime > pull_dt
581
+ local_modified = (
582
+ mtime.replace(microsecond=0) > pull_dt.replace(microsecond=0)
583
+ )
582
584
  except Exception:
583
585
  pass
584
586
 
@@ -843,7 +845,12 @@ def status() -> None:
843
845
  if pull_dt.tzinfo is None:
844
846
  pull_dt = pull_dt.replace(tzinfo=timezone.utc)
845
847
  mtime = datetime.fromtimestamp(yaml_path.stat().st_mtime, tz=timezone.utc)
846
- return "[yellow]modified[/yellow]" if mtime > pull_dt else "[green]clean[/green]"
848
+ # Compare at whole-second granularity. macOS APFS can commit the
849
+ # file mtime a few microseconds after Python records last_pull,
850
+ # causing a false-positive "modified" on freshly-pulled files.
851
+ # A sub-second difference means the pull itself wrote the file.
852
+ modified = mtime.replace(microsecond=0) > pull_dt.replace(microsecond=0)
853
+ return "[yellow]modified[/yellow]" if modified else "[green]clean[/green]"
847
854
  except Exception:
848
855
  return "[dim]unknown[/dim]"
849
856
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hadsync"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Home Assistant Dashboard Sync — pull, edit, and push Lovelace dashboards as code"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -40,7 +40,12 @@ function isLocallyModified(cwd: string, urlPath: string, lastPull: string): bool
40
40
  const yamlPath = path.join(cwd, urlPath, 'lovelace.yaml');
41
41
  const stat = fs.statSync(yamlPath);
42
42
  const pullTime = new Date(lastPull).getTime();
43
- return stat.mtimeMs > pullTime;
43
+ // Compare at whole-second granularity to avoid a false-positive on freshly
44
+ // pulled files: macOS APFS can commit the mtime a few microseconds after
45
+ // the pull records last_pull, making mtime > pullTime by a tiny amount.
46
+ const mtimeSec = Math.floor(stat.mtimeMs / 1000);
47
+ const pullSec = Math.floor(pullTime / 1000);
48
+ return mtimeSec > pullSec;
44
49
  } catch {
45
50
  return false;
46
51
  }
@@ -1 +0,0 @@
1
- __version__ = "0.2.2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes