···11+/**
22+ * Peek Component Version Management
33+ *
44+ * Semantic versioning, compatibility checking, and migration support.
55+ *
66+ * Usage:
77+ * import { version, checkCompatibility, migrate } from 'peek://app/components/version.js';
88+ *
99+ * console.log(version.current); // '1.0.0'
1010+ * checkCompatibility('>=0.9.0'); // true
1111+ */
1212+1313+// Current library version
1414+export const LIBRARY_VERSION = '1.0.0';
1515+1616+// Minimum supported version for migrations
1717+export const MIN_SUPPORTED_VERSION = '0.1.0';
1818+1919+// Version history for changelog/migrations
2020+const VERSION_HISTORY = [
2121+ {
2222+ version: '1.0.0',
2323+ date: '2026-01-29',
2424+ changes: [
2525+ 'Initial stable release',
2626+ 'Phase 1-4 components complete',
2727+ 'Theme system with light/dark built-in',
2828+ 'Extension system for content scripts',
2929+ 'Component registry with lazy loading'
3030+ ],
3131+ breaking: []
3232+ }
3333+];
3434+3535+/**
3636+ * Parse semantic version string
3737+ * @param {string} version - Version string (e.g., '1.2.3')
3838+ * @returns {{ major: number, minor: number, patch: number, prerelease?: string }}
3939+ */
4040+export function parseVersion(version) {
4141+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
4242+ if (!match) {
4343+ throw new Error(`Invalid version format: ${version}`);
4444+ }
4545+4646+ return {
4747+ major: parseInt(match[1], 10),
4848+ minor: parseInt(match[2], 10),
4949+ patch: parseInt(match[3], 10),
5050+ prerelease: match[4] || null
5151+ };
5252+}
5353+5454+/**
5555+ * Compare two versions
5656+ * @param {string} a - First version
5757+ * @param {string} b - Second version
5858+ * @returns {number} -1 if a < b, 0 if a == b, 1 if a > b
5959+ */
6060+export function compareVersions(a, b) {
6161+ const va = parseVersion(a);
6262+ const vb = parseVersion(b);
6363+6464+ if (va.major !== vb.major) return va.major - vb.major;
6565+ if (va.minor !== vb.minor) return va.minor - vb.minor;
6666+ if (va.patch !== vb.patch) return va.patch - vb.patch;
6767+6868+ // Prerelease versions are lower than release
6969+ if (va.prerelease && !vb.prerelease) return -1;
7070+ if (!va.prerelease && vb.prerelease) return 1;
7171+ if (va.prerelease && vb.prerelease) {
7272+ return va.prerelease.localeCompare(vb.prerelease);
7373+ }
7474+7575+ return 0;
7676+}
7777+7878+/**
7979+ * Check if version satisfies a constraint
8080+ * @param {string} version - Version to check
8181+ * @param {string} constraint - Constraint (e.g., '>=1.0.0', '^1.2.0', '~1.2.3')
8282+ * @returns {boolean}
8383+ */
8484+export function satisfies(version, constraint) {
8585+ const v = parseVersion(version);
8686+8787+ // Exact match
8888+ if (!constraint.match(/^[<>=^~]/)) {
8989+ return compareVersions(version, constraint) === 0;
9090+ }
9191+9292+ // Range operators
9393+ if (constraint.startsWith('>=')) {
9494+ return compareVersions(version, constraint.slice(2)) >= 0;
9595+ }
9696+ if (constraint.startsWith('<=')) {
9797+ return compareVersions(version, constraint.slice(2)) <= 0;
9898+ }
9999+ if (constraint.startsWith('>')) {
100100+ return compareVersions(version, constraint.slice(1)) > 0;
101101+ }
102102+ if (constraint.startsWith('<')) {
103103+ return compareVersions(version, constraint.slice(1)) < 0;
104104+ }
105105+106106+ // Caret (^) - compatible with version (same major)
107107+ if (constraint.startsWith('^')) {
108108+ const c = parseVersion(constraint.slice(1));
109109+ return v.major === c.major && compareVersions(version, constraint.slice(1)) >= 0;
110110+ }
111111+112112+ // Tilde (~) - approximately equivalent (same major.minor)
113113+ if (constraint.startsWith('~')) {
114114+ const c = parseVersion(constraint.slice(1));
115115+ return v.major === c.major && v.minor === c.minor && v.patch >= c.patch;
116116+ }
117117+118118+ return false;
119119+}
120120+121121+/**
122122+ * Check compatibility with current library version
123123+ * @param {string} constraint - Version constraint
124124+ * @returns {boolean}
125125+ */
126126+export function checkCompatibility(constraint) {
127127+ return satisfies(LIBRARY_VERSION, constraint);
128128+}
129129+130130+/**
131131+ * Check if a version is deprecated
132132+ * @param {string} version - Version to check
133133+ * @returns {boolean}
134134+ */
135135+export function isDeprecated(version) {
136136+ return compareVersions(version, MIN_SUPPORTED_VERSION) < 0;
137137+}
138138+139139+/**
140140+ * Get changelog for a version range
141141+ * @param {string} fromVersion - Starting version
142142+ * @param {string} toVersion - Ending version (default: current)
143143+ * @returns {Array}
144144+ */
145145+export function getChangelog(fromVersion, toVersion = LIBRARY_VERSION) {
146146+ return VERSION_HISTORY.filter(entry => {
147147+ const cmp = compareVersions(entry.version, fromVersion);
148148+ const cmpTo = compareVersions(entry.version, toVersion);
149149+ return cmp > 0 && cmpTo <= 0;
150150+ });
151151+}
152152+153153+/**
154154+ * Get breaking changes for a version range
155155+ * @param {string} fromVersion - Starting version
156156+ * @param {string} toVersion - Ending version (default: current)
157157+ * @returns {Array}
158158+ */
159159+export function getBreakingChanges(fromVersion, toVersion = LIBRARY_VERSION) {
160160+ const changelog = getChangelog(fromVersion, toVersion);
161161+ return changelog.flatMap(entry =>
162162+ entry.breaking.map(change => ({
163163+ version: entry.version,
164164+ change
165165+ }))
166166+ );
167167+}
168168+169169+/**
170170+ * Migration registry
171171+ */
172172+const migrations = new Map();
173173+174174+/**
175175+ * Register a migration
176176+ * @param {string} fromVersion - Source version
177177+ * @param {string} toVersion - Target version
178178+ * @param {Function} migrateFn - Migration function
179179+ */
180180+export function registerMigration(fromVersion, toVersion, migrateFn) {
181181+ const key = `${fromVersion}->${toVersion}`;
182182+ migrations.set(key, {
183183+ from: fromVersion,
184184+ to: toVersion,
185185+ migrate: migrateFn
186186+ });
187187+}
188188+189189+/**
190190+ * Find migration path between versions
191191+ * @param {string} fromVersion - Source version
192192+ * @param {string} toVersion - Target version
193193+ * @returns {Array} Array of migration steps
194194+ */
195195+export function findMigrationPath(fromVersion, toVersion) {
196196+ // Simple direct path lookup for now
197197+ // Could be extended to find multi-step paths
198198+ const key = `${fromVersion}->${toVersion}`;
199199+ const migration = migrations.get(key);
200200+ return migration ? [migration] : [];
201201+}
202202+203203+/**
204204+ * Run migrations
205205+ * @param {Object} data - Data to migrate
206206+ * @param {string} fromVersion - Source version
207207+ * @param {string} toVersion - Target version (default: current)
208208+ * @returns {Object} Migrated data
209209+ */
210210+export async function migrate(data, fromVersion, toVersion = LIBRARY_VERSION) {
211211+ const path = findMigrationPath(fromVersion, toVersion);
212212+213213+ if (path.length === 0 && fromVersion !== toVersion) {
214214+ console.warn(`No migration path from ${fromVersion} to ${toVersion}`);
215215+ return data;
216216+ }
217217+218218+ let result = data;
219219+ for (const step of path) {
220220+ result = await step.migrate(result);
221221+ }
222222+223223+ return result;
224224+}
225225+226226+/**
227227+ * Version object for easy access
228228+ */
229229+export const version = {
230230+ current: LIBRARY_VERSION,
231231+ min: MIN_SUPPORTED_VERSION,
232232+ parse: parseVersion,
233233+ compare: compareVersions,
234234+ satisfies,
235235+ checkCompatibility,
236236+ isDeprecated,
237237+ getChangelog,
238238+ getBreakingChanges,
239239+ migrate,
240240+ registerMigration,
241241+ history: VERSION_HISTORY
242242+};
243243+244244+/**
245245+ * Runtime version check - warns if incompatible
246246+ * @param {string} requiredVersion - Required version constraint
247247+ * @param {string} context - Context for warning message
248248+ */
249249+export function requireVersion(requiredVersion, context = '') {
250250+ if (!checkCompatibility(requiredVersion)) {
251251+ const msg = `Version mismatch${context ? ` in ${context}` : ''}: requires ${requiredVersion}, current is ${LIBRARY_VERSION}`;
252252+ console.warn(msg);
253253+ return false;
254254+ }
255255+ return true;
256256+}
257257+258258+/**
259259+ * Decorator for version-gated features
260260+ * @param {string} minVersion - Minimum version required
261261+ * @returns {Function} Decorator
262262+ */
263263+export function sinceVersion(minVersion) {
264264+ return function(target, propertyKey, descriptor) {
265265+ const original = descriptor.value;
266266+ descriptor.value = function(...args) {
267267+ if (!satisfies(LIBRARY_VERSION, `>=${minVersion}`)) {
268268+ throw new Error(`${propertyKey} requires version >=${minVersion}, current is ${LIBRARY_VERSION}`);
269269+ }
270270+ return original.apply(this, args);
271271+ };
272272+ return descriptor;
273273+ };
274274+}
275275+276276+export default version;
+4-4
notes/research-ui-componentry.md
···627627 - Usage examples
628628629629**Phase 5: Extension Distribution**
630630-1. Module Federation or shared `.js` bundle
631631-2. Component registry API
632632-3. Hot-reload for development
633633-4. Version management for stability
630630+1. Bundle configuration for ESM distribution (esbuild, rollup, vite compatible)
631631+2. Component registry API with lazy loading and dependency tracking
632632+3. Development utilities with hot-reload support
633633+4. Version management with semantic versioning and migration support
634634635635---
636636