2025-04-09 11:26:59 +00:00
# UID Generator Plugin for Obsidian
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Overview
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 20:08:01 +00:00
The UID Generator plugin for Obsidian provides tools to create and manage unique identifiers (UIDs) for your notes directly within their frontmatter metadata. It supports multiple generator algorithms — **UUID** (v4), **NanoID** (customizable length, alphabet, and separators), **ULID** (lexicographically sortable), and **Snowflake** (64-bit time-sortable distributed ID) — along with manual and automatic UID generation, customization of the metadata key and copy formats, and bulk operations within folders. This helps in creating stable, unique references for your notes, useful for linking, scripting, or external systems.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Features
2025-04-09 08:24:15 +00:00
2026-03-24 20:02:23 +00:00
* **Multiple Generator Algorithms:**
* **UUID** (v4) — Standard 36-character universally unique identifier.
* **NanoID** — Customizable length, alphabet, and optional separator characters injected at specific positions.
* **ULID** — 26-character, lexicographically sortable identifier that encodes creation time (useful for chronological ordering).
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 20:08:01 +00:00
* **Snowflake** — 64-bit time-sortable numeric ID (41-bit Unix timestamp + 10-bit node ID + 12-bit per-ms sequence). Node ID is auto-derived from the machine's MAC address on desktop and falls back to a random persistent value on mobile; both can be overridden manually.
2026-03-24 20:02:23 +00:00
* **Duplicate Detection:** An in-memory cache of existing UIDs ensures newly generated IDs are unique, with automatic retry on collision.
2025-04-09 11:26:59 +00:00
* **Generate/Update UID:** Manually generate a new UID for the current note, optionally overwriting any existing UID under the configured key.
* **Create UID If Missing:** Manually generate a UID for the current note *only* if one doesn't already exist.
* **Remove UID:** Manually remove the UID from the current note's frontmatter.
* **Copy UID:** Copy the UID of the current note to the clipboard.
* **Copy title + UID:** Copy the title and UID of the current note (or multiple selected notes) to the clipboard, using a customizable format.
* **Automatic UID Generation:** Automatically add a UID to notes upon creation or opening if they lack one.
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 20:08:01 +00:00
* Configurable scope (entire vault or one or more specific folders).
2025-04-09 11:26:59 +00:00
* Ability to exclude specific folders.
* Never overwrites existing UIDs during automatic generation.
* **Clear UIDs in Folder:** Remove all UIDs (using the configured key) from notes within a specified folder and its subfolders, with confirmation.
* **Customizable UID Key:** Choose the frontmatter key name for your UIDs (e.g., `uid` , `id` , `noteId` ).
* **Customizable Copy Format:** Define templates for how title/UID information is copied.
* **Ribbon Icon:** Quick access to "Create UID if missing" for the current note.
* **Context Menu Actions:**
* Right-click a folder: Copy titles+UIDs for all notes inside.
* Right-click a single note: Copy Title+UID.
* Right-click multiple selected notes: Copy titles+UIDs for the selection (uses undocumented `files-menu` event).
* **Folder Path Suggestions:** Autocomplete suggestions for folder paths in settings.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Installation
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
1. Download the plugin files (`main.js`, `manifest.json` , `styles.css` ) from the latest release on the GitHub repository (or build them yourself).
2. Create a new folder named `obsidian-note-uid-generator` inside your Obsidian vault's plugins folder (`YourVault/.obsidian/plugins/`).
3. Copy the downloaded `main.js` , `manifest.json` , and `styles.css` files into the `obsidian-note-uid-generator` folder.
4. Restart Obsidian or reload plugins.
5. Go to Obsidian Settings -> Community plugins.
6. Find "UID Generator" in the list of installed plugins and enable it.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Usage
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Commands
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
The plugin provides the following commands accessible from the command palette (Ctrl+P or Cmd+P):
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* ** `UID Generator: Generate/Update [YourKeyName] (Overwrites)` :** Creates a new UID for the current note. If a UID already exists under the configured key name (`[YourKeyName]`), it will be **replaced** .
* ** `UID Generator: Create [YourKeyName] if missing` :** Creates a new UID for the current note **only if** one doesn't already exist under the configured key name.
* ** `UID Generator: Remove [YourKeyName] from current note` :** Deletes the UID key and value from the current note's frontmatter.
* ** `UID Generator: Copy [YourKeyName] of current note` :** Copies the UID value to the clipboard. (Only available if the current note has a UID).
* ** `UID Generator: Copy title + [YourKeyName]` :** Copies the current note's title and UID using the configured format. (Always available if a note is open).
* ** `UID Generator: Copy titles+[YourKeyName]s for selected files` :** Copies the titles and UIDs for all *Markdown* files currently selected in the file explorer, using the configured format. (Only available when the file explorer is active and has Markdown files selected).
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Ribbon Icon
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* A ribbon icon (looks like a scan/search symbol) is added to the left sidebar.
* Clicking this icon performs the ** `Create [YourKeyName] if missing` ** action on the currently active note.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Context Menus
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* **Right-clicking a Folder** in the file explorer: Provides an option `Copy titles+[YourKeyName]s from "[FolderName]"` . This copies the titles and UIDs of all Markdown notes within that folder (and subfolders) according to your format settings.
* **Right-clicking a Single Markdown File:** Provides an option `Copy Title+[YourKeyName]` . This copies the title and UID for that specific file.
* **Right-clicking multiple selected files:** Provides an option `Copy titles+[YourKeyName]s for X selected` . This copies the titles and UIDs for all selected Markdown files.
* **Note:** This specific multi-file context menu relies on an *undocumented* Obsidian event (`files-menu`). While functional, it could potentially break in future Obsidian updates. The Command Palette option provides a more stable alternative for multi-file selection.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Settings
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
Access the plugin settings from Obsidian Settings -> Community Plugins -> UID Generator:
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* **General:**
* **UID Metadata Key:** Set the frontmatter key name used for storing UIDs (default: `uid` ). Avoid spaces.
2026-03-24 20:02:23 +00:00
* **UID Generator Type:**
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 20:08:01 +00:00
* **Generator Algorithm:** Choose between `UUID` , `NanoID` , `ULID` , or `Snowflake` .
2026-03-24 20:02:23 +00:00
* **NanoID Length:** (NanoID only) Length of the generated ID, excluding separators. Min 4, max 128. (Default: 21)
* **NanoID Alphabet:** (NanoID only) Characters used for ID generation. Must have at least 2 unique characters. (Default: `0-9A-Za-z` )
* **NanoID Separator Groups:** (NanoID only) Inject characters at specific positions in the generated ID. Positions can be negative (count from end). Multiple groups supported.
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 20:08:01 +00:00
* **Machine Node ID:** (Snowflake only) Read-only display of the 10-bit node ID derived from this machine — MAC-hashed on desktop, random persistent on mobile. Used for generated IDs unless a Custom Node ID is set below.
* **Custom Node ID:** (Snowflake only) Optional override (0– 1023). When set, takes precedence over the Machine Node ID. Leave empty to use the machine value.
2025-04-09 11:26:59 +00:00
* **Automatic UID Generation:**
* **Enable Automatic UID Generation:** Toggle the automatic creation of UIDs on/off.
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 20:08:01 +00:00
* **Generation Scope:** Choose `Entire Vault` or `Specific Folder(s)` .
* **Target Folders for Auto-Generation:** (Visible if Scope is 'Specific Folder(s)') Click "Manage folders" to open a modal where you can search, add, or remove folders that should be in scope for auto-generation. Notes in any of the listed folders (including subfolders) are eligible. Settings created with previous versions of the plugin are migrated automatically.
2025-04-09 11:26:59 +00:00
* **Excluded Folders:** Click "Manage Exclusions" to open a modal where you can search, add, or remove folders that should be ignored by automatic generation. The current list is displayed below the button.
* **Copy Format:**
* **Format (UID exists):** Define the template for copied text when a UID is present. Use placeholders `{title}` , `{uid}` , `{uidKey}` . (Default: `{title} - {uidKey}: {uid}` )
* **Format (UID missing):** Define the template for copied text when a UID is missing. Use placeholders `{title}` , `{uidKey}` . (Default: `{title} - No {uidKey}` )
* **Manual UID Clearing:**
* **Folder to clear UIDs from:** Specify the vault path for the bulk removal action. Uses folder path suggestions.
* **Clear UIDs Now:** Button to initiate the removal process for the specified folder.
* **Warning:** This is irreversible.
* A confirmation modal will appear.
* If automatic UID generation is enabled, it will be temporarily disabled as a safety measure when you confirm the deletion. You will be notified and can re-enable it afterwards.
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Example Usage
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* **Ensure a Note Has a Unique ID:** Open the note, open the command palette, run `UID Generator: Create uid if missing` .
* **Link Using UID:** Open a note, run `UID Generator: Copy uid` , paste the UID into another note's link or alias.
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 20:08:01 +00:00
* **Auto-Assign IDs to New Notes in Inbox:** Enable Automatic Generation, set Scope to 'Specific Folder(s)', click "Manage folders" and add `Inbox` (and any other target folders).
2025-04-09 11:26:59 +00:00
* **Clean Up Old IDs:** Set 'Folder to clear UIDs from' to `Archives/Old Project` , click 'Clear UIDs Now', confirm.
* **Get List of Project Notes with IDs:** Right-click the `Projects/Current Project` folder, select `Copy titles+uids from "Current Project"` .
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
## Development
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
For developers interested in contributing:
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Setup
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
1. Clone the GitHub repository to your local machine.
2. Navigate to the repository directory.
3. Install dependencies: `npm install` (or `yarn install` ).
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
### Build
2025-04-09 08:24:15 +00:00
2025-04-09 11:26:59 +00:00
* To compile the TypeScript code to JavaScript (`main.js`), run: `npm run build` (or `yarn build` ).
* For development, you can often use a watch command like `npm run dev` (if configured in `package.json` ) to automatically rebuild on changes.
### Code Structure
* `src/main.ts` : Main plugin class (`UIDGenerator`), `onload` , `onunload` , registration logic.
* `src/settings.ts` : Settings interface (`UIDGeneratorSettings`), defaults (`DEFAULT_SETTINGS`), and the settings tab UI class (`UIDSettingTab`).
* `src/commands.ts` : Contains handler functions for commands, context menu actions, and event listeners.
* `src/uidUtils.ts` : Core utility functions for generating, getting, setting, removing, and formatting UIDs.
* `src/ui/` : Contains UI component classes:
* `FolderSuggest.ts`
* `ConfirmationModal.ts`
* `FolderExclusionModal.ts`
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 20:08:01 +00:00
* `FolderSelectionModal.ts`
2025-04-09 11:26:59 +00:00
* `src/obsidian.d.ts` : Contains TypeScript declarations for undocumented Obsidian APIs used (like `files-menu` ).
## Contributing
2025-04-09 13:06:56 +00:00
Contributions, issues, and feature requests are welcome! Please feel free to check the [issues page ](https://github.com/Netajam/obsidian_note_uid_generator/issues ) or submit a pull request on the [GitHub repository ](https://github.com/Netajam/obsidian_note_uid_generator ).
2025-04-09 11:26:59 +00:00
## License
2025-04-09 13:06:56 +00:00
This plugin is licensed under the OBSD License. See the LICENSE file for details.