@devosurf/vynt 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devosurf/vynt",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "bin",
package/src/bridge.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"
2
2
  import { watch, type FSWatcher } from "node:fs"
3
- import { readFile } from "node:fs/promises"
3
+ import { readFile, unlink } from "node:fs/promises"
4
4
  import { dirname, resolve } from "node:path"
5
5
  import { fileURLToPath } from "node:url"
6
6
  import { applySelectionsToWorkspace, getLatestSnapshot, rollbackWorkspaceToSnapshot } from "./switch.js"
@@ -187,12 +187,18 @@ function markVariantSelectedInState(state: VariantStore, objectiveId: string, va
187
187
  }
188
188
  }
189
189
 
190
- function finalizeVariantInObjectiveState(state: VariantStore, objectiveId: string, variantId: string): VariantStore {
190
+ function finalizeVariantInObjectiveState(
191
+ state: VariantStore,
192
+ objectiveId: string,
193
+ variantId: string,
194
+ ): { nextState: VariantStore; removedVariants: VariantArtifact[] } {
191
195
  const objectiveItem = findObjective(state, objectiveId)
192
- findVariant(objectiveItem, variantId)
196
+ const winner = findVariant(objectiveItem, variantId)
193
197
  const now = new Date().toISOString()
198
+ const removedVariants = objectiveItem.variants.filter((variant) => variant.id !== variantId)
194
199
 
195
200
  return {
201
+ nextState: {
196
202
  ...state,
197
203
  activeProfileId: undefined,
198
204
  objectives: state.objectives.map((item) => {
@@ -204,23 +210,49 @@ function finalizeVariantInObjectiveState(state: VariantStore, objectiveId: strin
204
210
  winnerVariantId: variantId,
205
211
  status: "finalized" as const,
206
212
  updatedAt: now,
207
- variants: objectiveItem.variants.map((variant) => {
208
- if (variant.id === variantId) {
209
- return {
210
- ...variant,
211
- status: "selected" as const,
212
- updatedAt: now,
213
- }
214
- }
215
-
216
- return {
217
- ...variant,
218
- status: "archived" as const,
213
+ variants: [
214
+ {
215
+ ...winner,
216
+ status: "selected" as const,
219
217
  updatedAt: now,
220
- }
221
- }),
218
+ },
219
+ ],
222
220
  }
223
221
  }),
222
+ },
223
+ removedVariants,
224
+ }
225
+ }
226
+
227
+ function collectPatchFiles(state: VariantStore): Set<string> {
228
+ const files = new Set<string>()
229
+ for (const objective of state.objectives) {
230
+ for (const variant of objective.variants) {
231
+ files.add(variant.patchFile)
232
+ }
233
+ }
234
+ return files
235
+ }
236
+
237
+ async function pruneRemovedVariantPatchFiles(
238
+ nextState: VariantStore,
239
+ removedVariants: VariantArtifact[],
240
+ ): Promise<void> {
241
+ if (removedVariants.length === 0) return
242
+ const retainedPatchFiles = collectPatchFiles(nextState)
243
+ const deleteCandidates = new Set<string>()
244
+
245
+ for (const variant of removedVariants) {
246
+ if (!retainedPatchFiles.has(variant.patchFile)) {
247
+ deleteCandidates.add(variant.patchFile)
248
+ }
249
+ }
250
+
251
+ for (const patchFile of deleteCandidates) {
252
+ try {
253
+ await unlink(patchFile)
254
+ } catch {
255
+ }
224
256
  }
225
257
  }
226
258
 
@@ -743,7 +775,9 @@ export async function startBridgeServer(options: StartBridgeServerOptions): Prom
743
775
  },
744
776
  ])
745
777
 
746
- const nextState = finalizeVariantInObjectiveState(state, target.objective.id, target.variant.id)
778
+ const finalized = finalizeVariantInObjectiveState(state, target.objective.id, target.variant.id)
779
+ await pruneRemovedVariantPatchFiles(finalized.nextState, finalized.removedVariants)
780
+ const nextState = finalized.nextState
747
781
  await saveState(projectRoot, nextState)
748
782
 
749
783
  emitEvent("finalize.succeeded", {
package/src/cli.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process"
2
- import { access, mkdir, readFile, writeFile } from "node:fs/promises"
2
+ import { access, mkdir, readFile, unlink, writeFile } from "node:fs/promises"
3
3
  import { basename, join, resolve } from "node:path"
4
4
  import process from "node:process"
5
5
  import { createInterface } from "node:readline/promises"
@@ -544,6 +544,38 @@ function findProfile(state: VariantStore, profileId: string): PreviewProfile {
544
544
  return found
545
545
  }
546
546
 
547
+ function collectPatchFiles(state: VariantStore): Set<string> {
548
+ const files = new Set<string>()
549
+ for (const objective of state.objectives) {
550
+ for (const variant of objective.variants) {
551
+ files.add(variant.patchFile)
552
+ }
553
+ }
554
+ return files
555
+ }
556
+
557
+ async function pruneRemovedVariantPatchFiles(
558
+ nextState: VariantStore,
559
+ removedVariants: VariantArtifact[],
560
+ ): Promise<void> {
561
+ if (removedVariants.length === 0) return
562
+ const retainedPatchFiles = collectPatchFiles(nextState)
563
+ const deleteCandidates = new Set<string>()
564
+
565
+ for (const variant of removedVariants) {
566
+ if (!retainedPatchFiles.has(variant.patchFile)) {
567
+ deleteCandidates.add(variant.patchFile)
568
+ }
569
+ }
570
+
571
+ for (const patchFile of deleteCandidates) {
572
+ try {
573
+ await unlink(patchFile)
574
+ } catch {
575
+ }
576
+ }
577
+ }
578
+
547
579
  function findVariantAcrossObjectives(
548
580
  state: VariantStore,
549
581
  variantId: string,
@@ -829,17 +861,26 @@ function createProgram(cwd: string): Command {
829
861
  .action(async (objectiveId: string, variantId: string) => {
830
862
  const state = requireState(await loadState(cwd))
831
863
  const current = findObjective(state, objectiveId)
832
- findVariant(current, variantId)
864
+ const winner = findVariant(current, variantId)
865
+ const removedVariants = current.variants.filter((variant) => variant.id !== variantId)
833
866
 
834
867
  const now = new Date().toISOString()
835
868
  const next = upsertObjective(state, {
836
869
  ...current,
837
870
  status: "finalized",
838
871
  winnerVariantId: variantId,
839
- activeVariantId: current.activeVariantId ?? variantId,
872
+ activeVariantId: variantId,
840
873
  updatedAt: now,
874
+ variants: [
875
+ {
876
+ ...winner,
877
+ status: "selected",
878
+ updatedAt: now,
879
+ },
880
+ ],
841
881
  })
842
882
 
883
+ await pruneRemovedVariantPatchFiles(next, removedVariants)
843
884
  await saveState(cwd, next)
844
885
  process.stdout.write(`Objective ${objectiveId} finalized with winner ${variantId}\n`)
845
886
  })
@@ -576,13 +576,35 @@
576
576
  objectiveOverlayRoot.style.width = `${Math.max(0, width)}px`;
577
577
  objectiveOverlayRoot.style.height = `${Math.max(0, height)}px`;
578
578
 
579
- const panelBottom = -50;
580
- if (y + height + panelBottom + 48 > window.innerHeight) {
581
- objectivePanel.style.bottom = "auto";
582
- objectivePanel.style.top = "-50px";
579
+ const panelGap = 8;
580
+ const panelWidth = 220;
581
+ const panelHeight = 40;
582
+ const rightSpace = window.innerWidth - (x + width);
583
+ const leftSpace = x;
584
+ const alignedTop = Math.max(0, Math.min(Math.max(0, height - panelHeight), (height - panelHeight) / 2));
585
+
586
+ objectivePanel.style.bottom = "auto";
587
+ objectivePanel.style.top = `${alignedTop}px`;
588
+
589
+ if (rightSpace >= panelWidth + panelGap) {
590
+ objectivePanel.style.left = `${width + panelGap}px`;
591
+ objectivePanel.style.transform = "none";
592
+ return;
593
+ }
594
+
595
+ if (leftSpace >= panelWidth + panelGap) {
596
+ objectivePanel.style.left = `${-panelGap}px`;
597
+ objectivePanel.style.transform = "translateX(-100%)";
598
+ return;
599
+ }
600
+
601
+ objectivePanel.style.left = "50%";
602
+ objectivePanel.style.transform = "translateX(-50%)";
603
+ if (y + height + panelGap + panelHeight > window.innerHeight) {
604
+ objectivePanel.style.top = `${-panelGap}px`;
605
+ objectivePanel.style.transform = "translate(-50%, -100%)";
583
606
  } else {
584
- objectivePanel.style.top = "auto";
585
- objectivePanel.style.bottom = "-50px";
607
+ objectivePanel.style.top = `${height + panelGap}px`;
586
608
  }
587
609
  }
588
610