@pygmalionjs/pygmalion 0.6.33 → 0.6.34

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.
@@ -27,6 +27,8 @@ export interface CaptureSupplyFrame {
27
27
  recipe?: RoutePreviewFrameRecipe;
28
28
  }
29
29
  export interface CaptureSupplyPlan {
30
+ /** Canvas that owned the viewport when this plan was built. */
31
+ activeCanvas?: string;
30
32
  /** Chunks of the active canvas, nearest first. */
31
33
  active: CaptureSupplyFrame[][];
32
34
  /** Chunks of the other canvases, in the order the frames were given. */
@@ -27,6 +27,21 @@ export declare function mirrorBootRetryDelayMs(attempt: number): number;
27
27
  export interface MirrorGateStatus {
28
28
  state: string;
29
29
  }
30
+ /** The idle status fields that can identify an immutable source before checkout. */
31
+ export interface IdleMirrorSourceStatus extends MirrorGateStatus {
32
+ sourceRef: string | null;
33
+ }
34
+ /**
35
+ * Returns an immutable source revision that can safely create the first lazy
36
+ * preview demand.
37
+ *
38
+ * An idle mirror has no checked-out `commit` yet. When it was configured with
39
+ * a full Git object id, however, that ref already names the exact revision the
40
+ * catalog endpoint will verify after preparation. Named refs, worktree mode,
41
+ * and abbreviated ids can move or be ambiguous, so they remain pending until
42
+ * the server reports the resolved commit.
43
+ */
44
+ export declare function idleMirrorSourceRevision(status: IdleMirrorSourceStatus): string | null;
30
45
  export interface SettleMirrorStatusOptions<S extends MirrorGateStatus> {
31
46
  /** Status of the initial request; returned as-is when already settled. */
32
47
  initial: S;
@@ -5,6 +5,7 @@ export interface RoutePreviewArtifactStatusInput {
5
5
  resolution: RoutePreviewArtifactResolution;
6
6
  captureStatus?: RoutePreviewArtifactV3Status;
7
7
  diagnostic?: string;
8
+ liveInteractionReady?: boolean;
8
9
  }
9
10
  export interface RoutePreviewArtifactStatusPresentation {
10
11
  title: string;
@@ -45,4 +46,4 @@ export declare function resolveStaticRoutePreviewSurface({ hasScreenshot, hasDom
45
46
  preferDom?: boolean;
46
47
  }): StaticRoutePreviewSurface;
47
48
  /** Turns artifact freshness and capture state into one unambiguous frame badge. */
48
- export declare function resolveRoutePreviewArtifactStatus({ stale, resolution, captureStatus, diagnostic, }: RoutePreviewArtifactStatusInput): RoutePreviewArtifactStatusPresentation | null;
49
+ export declare function resolveRoutePreviewArtifactStatus({ stale, resolution, captureStatus, diagnostic, liveInteractionReady, }: RoutePreviewArtifactStatusInput): RoutePreviewArtifactStatusPresentation | null;
@@ -843,6 +843,9 @@ export async function acquireExactDevMirrorSourceLease({
843
843
  return {
844
844
  sourceRoot: exactSourceRoot,
845
845
  sourceRevision: expectedRevision,
846
+ // Synchronization and reaping honor the heartbeat lease until release,
847
+ // so retention may safely inspect this root while the caller holds it.
848
+ sourceRootStable: true,
846
849
  release,
847
850
  };
848
851
  } catch (error) {
@@ -788,6 +788,12 @@ export function pygmalionPreviewArtifactPlugin({
788
788
  return { sourceRevision, sourceRoot: lease.sourceRoot };
789
789
  }
790
790
 
791
+ function retentionSourceContextFromLease(lease, sourceRevision) {
792
+ return lease?.sourceRootStable === true
793
+ ? sourceContextFromLease(lease, sourceRevision)
794
+ : undefined;
795
+ }
796
+
791
797
  /**
792
798
  * Refines a conservative cache decision only while the requested checkout is
793
799
  * pinned. Fingerprint hits, recipe mismatches, and frames without observed
@@ -872,6 +878,11 @@ export function pygmalionPreviewArtifactPlugin({
872
878
  const progress = startCaptureProgress();
873
879
  try {
874
880
  const generated = await withCaptureLease(sourceRevision, async (lease) => {
881
+ const sourceContext = sourceContextFromLease(lease, sourceRevision);
882
+ const retentionSourceContext = retentionSourceContextFromLease(
883
+ lease,
884
+ sourceRevision,
885
+ );
875
886
  const captured = await generateArtifact(
876
887
  {
877
888
  namespace,
@@ -889,10 +900,10 @@ export function pygmalionPreviewArtifactPlugin({
889
900
  'Generated preview artifact identity does not match the request.',
890
901
  );
891
902
  }
892
- const observedDependenciesByFrame = lease?.sourceRoot
903
+ const observedDependenciesByFrame = sourceContext
893
904
  ? await artifactStore.observeArtifactDependencies(
894
905
  captured,
895
- lease.sourceRoot,
906
+ sourceContext.sourceRoot,
896
907
  )
897
908
  : undefined;
898
909
  await validateSourceLease(lease, sourceRevision);
@@ -900,6 +911,9 @@ export function pygmalionPreviewArtifactPlugin({
900
911
  await artifactStore.publishArtifact(captured, {
901
912
  sourceRevision,
902
913
  recordRevision: true,
914
+ ...(retentionSourceContext
915
+ ? { sourceContext: retentionSourceContext }
916
+ : {}),
903
917
  ...(observedDependenciesByFrame
904
918
  ? { observedDependenciesByFrame }
905
919
  : {}),
@@ -941,6 +955,10 @@ export function pygmalionPreviewArtifactPlugin({
941
955
  try {
942
956
  return await withCaptureLease(sourceRevision, async (lease) => {
943
957
  const sourceContext = sourceContextFromLease(lease, sourceRevision);
958
+ const retentionSourceContext = retentionSourceContextFromLease(
959
+ lease,
960
+ sourceRevision,
961
+ );
944
962
  const before = await artifactStore.readFrameSelection(
945
963
  namespace,
946
964
  sourceRevision,
@@ -976,10 +994,10 @@ export function pygmalionPreviewArtifactPlugin({
976
994
  'Generated preview artifact frames do not match the request.',
977
995
  );
978
996
  }
979
- const observedDependenciesByFrame = lease?.sourceRoot
997
+ const observedDependenciesByFrame = sourceContext
980
998
  ? await artifactStore.observeArtifactDependencies(
981
999
  captured,
982
- lease.sourceRoot,
1000
+ sourceContext.sourceRoot,
983
1001
  )
984
1002
  : undefined;
985
1003
  await validateSourceLease(lease, sourceRevision);
@@ -988,6 +1006,9 @@ export function pygmalionPreviewArtifactPlugin({
988
1006
  sourceRevision,
989
1007
  recordRevision: false,
990
1008
  frameRequests: remaining,
1009
+ ...(retentionSourceContext
1010
+ ? { sourceContext: retentionSourceContext }
1011
+ : {}),
991
1012
  ...(observedDependenciesByFrame
992
1013
  ? { observedDependenciesByFrame }
993
1014
  : {}),
@@ -596,7 +596,10 @@ export function createPreviewArtifactStore({
596
596
  * is what keeps a concurrent reader from losing a bundle it is about to
597
597
  * fetch.
598
598
  */
599
- const evictToBudget = async (manifest, { at, sourceContext } = {}) => {
599
+ const evictToBudget = async (
600
+ manifest,
601
+ { at, sourceContext, admittedFrameObjects = new Set() } = {},
602
+ ) => {
600
603
  const referenced = new Map();
601
604
  const remember = async (hash) => {
602
605
  if (!referenced.has(hash)) referenced.set(hash, await objectByteSize(hash));
@@ -627,7 +630,13 @@ export function createPreviewArtifactStore({
627
630
  candidates.push({
628
631
  kind: 'frame',
629
632
  entry,
630
- rank: EVICTION_RANK[await classifyFrameEntry(entry, sourceContext)],
633
+ // A caller that supplied normalized observations recorded from the
634
+ // artifact's own snapshot has already paid to produce this object.
635
+ // Protect that admission from older unknown entries without rereading
636
+ // a source root that may not be physically pinned.
637
+ rank: admittedFrameObjects.has(entry.object)
638
+ ? EVICTION_RANK.valid
639
+ : EVICTION_RANK[await classifyFrameEntry(entry, sourceContext)],
631
640
  // Protection within a rank, never across it: the last copy of a frame
632
641
  // is worth more than a surplus copy, but an invalid sole entry is still
633
642
  // garbage and goes before a valid one.
@@ -872,6 +881,7 @@ export function createPreviewArtifactStore({
872
881
  ...(stored?.frames ?? []),
873
882
  ].map((entry) => entry.object),
874
883
  );
884
+ const admittedFrameObjects = new Set();
875
885
  if (recordRevision && sourceRevision) {
876
886
  const wholeObject = await writeObject(artifact);
877
887
  const nextRevision = {
@@ -922,6 +932,7 @@ export function createPreviewArtifactStore({
922
932
  generation,
923
933
  ...(observed ? { sourceFiles: observed } : {}),
924
934
  };
935
+ if (observed) admittedFrameObjects.add(object);
925
936
  const alreadyRecorded = frames.some(
926
937
  (entry) =>
927
938
  entry.id === nextFrame.id &&
@@ -941,6 +952,7 @@ export function createPreviewArtifactStore({
941
952
  const trimmed = await evictToBudget(manifest, {
942
953
  at,
943
954
  sourceContext: exactSourceContext,
955
+ admittedFrameObjects,
944
956
  });
945
957
  manifest = trimmed.dropped
946
958
  ? { ...trimmed.manifest, lastEviction: trimmed.dropped }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pygmalionjs/pygmalion",
3
- "version": "0.6.33",
3
+ "version": "0.6.34",
4
4
  "description": "Code-backed DOM design sandbox and visual QA editor",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {