Commit graph

23 commits

Author SHA1 Message Date
Netajam
49287280c7 Use --legacy-peer-deps in release workflow
The release CI's `npm install` ran into the @types/node peer-dep
mismatch between the project's pinned `@types/node@^16` and
vitest@1.6.1's peerOptional `@types/node@^18 || >=20`. Locally we
work around this with --legacy-peer-deps; mirror that flag in CI so
the release build can produce main.js.
2026-04-27 22:12:56 +02:00
Netajam
0e2c21fed7 1.3.0 2026-04-27 22:11:13 +02:00
Netajam
a8234a53b4 Mention Snowflake in plugin descriptions 2026-04-27 22:11:08 +02:00
Keonsoon Hwang
3aa345026a
feat: add Snowflake ID generator and multi-folder auto-generation scope (#4)
* feat: add Snowflake ID generator and multi-folder auto-generation scope

- Add Snowflake ID generator (64-bit, time-sortable, distributed)
  - Uses Unix timestamp directly (no custom epoch)
  - 41-bit timestamp + 10-bit node ID + 12-bit sequence
  - Auto-detect node ID from MAC address via os.networkInterfaces()
  - Falls back to a random persistent value on mobile
  - Node ID manually overridable in settings
- Support multiple target folders for auto-generation scope
  - Replace single autoGenerationFolder string with autoGenerationFolders array
  - Automatic migration of existing single-folder setting to array
  - New FolderSelectionModal with search and add/remove UI
- Bump build target to ES2020 for BigInt support
- Fix Automatic uid generation description rendering (use setDesc on heading)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Tighten Snowflake init and bound the per-ms spin-wait

src/main.ts onload:
- Drop the redundant outer `uidGenerator === 'snowflake' || ...` guard.
  The inner branch only ran when `snowflakeAutoDetectNodeId` was true,
  so the outer disjunction was dead. Restrict initialization to the
  case where the user has actually selected Snowflake.
- Skip `saveSettings()` when `detectNodeId()` returns the value already
  stored. The previous code wrote settings to disk on every plugin
  load, even when nothing had changed.

src/uidUtils.ts:
- Drop the inferrable `: bigint` annotations on the module-level
  Snowflake state (lint).
- Bound the sequence-overflow spin-wait to a few ms. The previous
  `while (timestamp <= snowflakeLastTimestamp)` had no exit if the
  system clock was stuck or jumped backwards (NTP, suspend/resume).
- Treat a backwards clock jump explicitly: bump the stored timestamp
  forward by one ms instead of regressing or spinning. IDs stay
  monotonic across the jump; nodeID/sequence layout is unchanged.
- Export a test-only `_resetSnowflakeState` so unit tests can reset
  module state between cases.

* Update test fakes and cover Snowflake + multi-folder scope

The previous test suite was authored against the single-folder
`autoGenerationFolder` setting. The PR replaces that with the
`autoGenerationFolders` array, which broke 11 existing tests
(the array was undefined in the fake's default settings).

tests/fakes/app.ts:
- Initialize `autoGenerationFolders: []` and the new Snowflake
  fields in the fake's DEFAULT_SETTINGS so plugin code that reads
  them in tests doesn't crash on `undefined`.
- `snowflakeAutoDetectNodeId` defaults to `false` in the fake to
  keep tests deterministic.

src/commands.test.ts:
- Rewrite every `autoGenerationFolder: 'X'` case to use
  `autoGenerationFolders: ['X']`. The legacy field is now only
  consulted by the one-shot migration in loadSettings.
- Add a `multi-folder scope` block that verifies in/out-of-scope
  decisions across multiple configured folders, nested subfolders,
  whitespace handling, and blank entries.

src/uidUtils.test.ts:
- Add a `Snowflake ID generator` block: numeric-string output,
  node-ID encoding (including >1023 clamp), per-ms sequence
  increment, sequence reset on next ms, monotonic output across
  many calls in one ms, and recovery from a backwards clock jump.
- Reset module state between cases via `_resetSnowflakeState`.

* Document Snowflake generator and multi-folder auto-generation scope

- Add Snowflake to the algorithm overview, noting the bit layout and
  the desktop-MAC / mobile-random node ID derivation.
- Update the Auto-Generation section to reference Specific Folder(s),
  the new "Manage folders" modal, and the automatic migration of the
  legacy single-folder setting.
- Add the new Snowflake settings (Auto-detect Node ID, Node ID) to
  the Settings reference.
- Update the "Auto-Assign IDs to New Notes in Inbox" example for the
  new modal-based folder picker.
- List src/ui/FolderSelectionModal.ts in the Code Structure section.

* Show Machine and Custom Node IDs as separate fields

Replace the single Node ID input + auto-detect toggle with two
distinct fields, so users can see at a glance whether the value used
for generated IDs is the machine default or an explicit override:

- Machine Node ID (read-only): the MAC-derived value on desktop, or
  the random persistent fallback on mobile. Always visible and always
  current — re-resolved on plugin load and on every settings render
  via `resolveAutoDetectedNodeId`.
- Custom Node ID (text input): optional override. When set, it takes
  precedence over the machine value at generation time. Clearing the
  field falls back to the machine value.

Settings model:
- Add `snowflakeNodeIdOverride: number | null`.
- Remove the `snowflakeAutoDetectNodeId` toggle from the active model;
  it lives on as an optional legacy field for one-shot migration.
- One-shot migration: a previous build's `snowflakeAutoDetectNodeId
  === false` with a non-zero `snowflakeNodeId` is preserved as a
  Custom Node ID, then the legacy field is deleted.

Generator: `generateRawUID` now resolves
`override ?? snowflakeNodeId` per call, so changes to either field
take effect immediately.

Tests: add coverage for both precedence directions (override wins;
null override falls through to the machine value).

Descriptions in the settings UI flip between "currently used" and
"currently NOT used / overridden below" so the precedence is obvious
without reading the README.

* Cover the Snowflake auto-detect resolution paths

`resolveAutoDetectedNodeId` is the entry point used by main.ts
onload and the settings UI to keep the cached machine Node ID in
sync with the hardware. It had zero unit coverage.

To make the no-MAC (mobile) path testable without depending on the
test machine, accept an optional `detect` parameter that defaults to
`detectNodeId`. Production callers don't pass it.

New tests cover:
- Detected differs from stored → returns the detected value.
- Detected matches stored → returns null (no churn).
- Mobile + stored 0 → picks a random 10-bit value.
- Mobile + stored already set → returns null (preserves the random pick).
- Math.random() at extreme values stays in 0–1023 (Math.floor guard).

Plus a smoke test for `detectNodeId`: never throws on the host
running tests, returns either null or a number in 0–1023, and is
deterministic across calls on the same machine.

These exercise the cross-platform safety we rely on for Windows,
Linux, macOS desktop and the iOS/Android Obsidian Mobile fallback.

* Prevent the mobile random Node ID from colliding with the unset sentinel

The previous code picked a random Node ID in 0..1023 when no MAC was
available. `stored === 0` is also the sentinel for "not yet picked",
so a 1-in-1024 unlucky roll landed on 0, made the saved settings look
unset on next load, and triggered a re-roll. The user would silently
get a new node ID on every restart, breaking ID stability across
sessions and undermining the "random persistent" guarantee Snowflake
relies on for mobile.

Restrict the random fallback to 1..1023. The valid Snowflake node-ID
range is unchanged (manual/MAC-derived values can still be 0); only
the random pick is excluded from 0. Losing one out of 1024 possible
values in the random pool is negligible.

Add tests:
- The mobile path never returns 0 (lock the sentinel-collision fix in).
- A two-load simulation: first call picks a value, second call returns
  null — i.e. the saved value is genuinely persistent.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Netajam <leonard.tavoli@gmail.com>
2026-04-27 22:08:01 +02:00
Netajam
a6248b32b5 Restore working ESLint setup and clear pre-existing lint debt
Adds eslint@^8 as a devDependency (the .eslintrc has long referenced
@typescript-eslint plugins, but eslint itself was never installed, so
`npx eslint` failed out of the box) and wires up an `npm run lint`
script for parity with the existing build/test scripts.

Fixes the 7 errors and 2 warnings that surface once lint actually runs:
- prefer-const on `isInside` / `outputLines` (commands.ts).
- no-inferrable-types on `overwrite: boolean = false` (uidUtils.ts).
- no-unused-vars: removes `filesWithUidCount`, which was incremented
  in two copy handlers but never read.
- no-explicit-any: silenced file-wide in src/typings/obsidian.d.ts with
  a comment explaining the intent — these declarations mirror Obsidian's
  upstream d.ts conventions for undocumented APIs (callbacks return any,
  opaque ctx is any), and refining them would diverge from upstream.
2026-04-27 20:53:50 +02:00
Netajam
3fd4a2ff97 Introduce Vitest test suite for UID generation and command behavior
Sets up Vitest with an aliased obsidian stub so tests can load production
modules without the real Electron-bound API, and a fake-app builder
(in-memory vault, frontmatter, processFrontMatter) for testing commands
that touch the Obsidian app object.

Coverage:
- uidUtils: generator selection (UUID/NanoID/ULID), NanoID separator
  injection, collision retry, getUIDFromFile/setUID/removeUID across
  edge cases (custom uidKey, legacy key cleanup, error paths).
- commands: settings-driven decisions (autoGenerateUid toggle, scope=vault
  vs folder, exclusions including multi-entry / whitespace / blank /
  trailing-slash variants), bulk vs per-file parity, copy/clear flows,
  and degenerate "everything excluded" combinations.

Includes one intentionally failing it() at commands.test.ts:225 that
documents the inconsistency between handleClearUIDsInFolder (treats "/"
as the whole vault) and the exclusion matcher (treats "/" as inert).

The build's tsc step now excludes tests/ and *.test.ts; Vitest type-checks
test code via esbuild at run time. Run with: npm test (or npm run test:watch).
2026-04-27 20:53:21 +02:00
Raphael Hubain
c1987a46f1
Fix: '/' in excluded folders now matches every file (#5)
When the user adds the vault root ('/') to the auto-generation
exclusion list, no file was actually excluded. `normalizePath('/')`
returns `'/'`, but file paths in Obsidian never start with a slash
(e.g. `note.md`, `sub/note.md`), so the existing checks
`path.startsWith('/' + '/')` and `path === '/'` could never match.

Treat `normEx === '/'` as "matches every file in the vault". This is
consistent with how the same file already interprets the vault root
in `getFilesInFolder` and `clearUIDsInFolder` (commands.ts:134, :274),
where `targetPath === '/'` expands to every markdown file.

Applied to both exclusion checks: the per-file auto-generation handler
(`handleAutoGenerateUid`) and the bulk scan (`handleAddMissingUidsInScope`).
2026-04-27 20:46:51 +02:00
Netajam
70d82e700b Update README with NanoID, ULID, and duplicate detection docs (v1.2.1)
Document the three generator algorithms (UUID, NanoID, ULID),
NanoID configuration options, and the duplicate detection feature.
Update plugin descriptions in manifest.json and package.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:02:23 +01:00
Netajam
94046153b6 Add ULID generator support (v1.2.0)
Add ULID as a third generator option alongside UUID and NanoID.
ULIDs are 26-character, lexicographically sortable identifiers
that encode creation time — useful for chronological ordering.

Update plugin description to reflect all three generator types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 20:39:01 +01:00
Netajam
b931ccab9b Bump version to 1.1.0 — NanoID support
Fix package.json version field (was description string) and move
description to the correct field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 20:30:08 +01:00
Netajam
b1ba54983f Show user-facing notice with combination count when UID collision retries are exhausted
Instead of silently returning a duplicate ID, display a Notice
showing the number of possible combinations and advising the user
to increase NanoID length or alphabet size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 20:19:26 +01:00
Netajam
8e4644cfe5 Review improvements on NanoID PR: validation, caching, and duplicate detection
- Cache customAlphabet generator to avoid recreating on every call
- Add input validation: NanoID length (4-128), alphabet (min 2 unique chars), single-char separators
- Add UID duplicate detection via in-memory Set + filePath→uid Map (O(1) lookups)
- Build cache once on plugin load, keep in sync on create/delete/external edits
- Listen to metadataCache.on('changed') to detect external frontmatter modifications
- Rebuild cache when uidKey setting changes
- Use path map for reliable cache cleanup on file delete
- Fix escaped apostrophes in JSDoc comments
- Revert unrelated quote-style changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 20:09:18 +01:00
stefano-HH
781a449690 Add nanoID 2026-03-18 17:35:05 +01:00
Netajam
9f619492d8 UID -> uid 2025-05-14 11:36:18 +02:00
Netajam
0771ec7673 changes to pass review 2025-05-02 14:23:53 +02:00
Netajam
64c2e0f88f v 1.0.6 2025-04-10 10:43:50 +02:00
Netajam
961eac27b9 renaming Copytitle->CopytTitle 2025-04-09 18:39:27 +02:00
Netajam
1a4aadfc17 Feature: Generate Missing UIDs 2025-04-09 17:59:12 +02:00
Netajam
b5cb08b7dd 1.0.5 2025-04-09 16:05:37 +02:00
Netajam
1fbbc43093
Create release.yml 2025-04-09 15:28:24 +02:00
Netajam
65727da2d9 V 1.0.1 2025-04-09 15:06:56 +02:00
Netajam
85f274f314 V 1.0.0 2025-04-09 13:26:59 +02:00
Netajam
44056d29a0
Initial commit 2025-04-09 10:24:15 +02:00