pi-studio 0.5.10 → 0.5.11

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/index.ts +86 -1
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.11] — 2026-03-15
8
+
9
+ ### Added
10
+ - Studio tabs now show a title attention marker like `● Response ready` or `● Critique ready` when a Studio-started model request finishes while the tab is unfocused, and clear that marker when the tab regains focus or the next Studio request starts.
11
+
7
12
  ## [0.5.10] — 2026-03-14
8
13
 
9
14
  ### Fixed
package/index.ts CHANGED
@@ -3539,6 +3539,10 @@ ${cssVarsBlock}
3539
3539
  let contextPercent = null;
3540
3540
  let updateInstalledVersion = null;
3541
3541
  let updateLatestVersion = null;
3542
+ let windowHasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : true;
3543
+ let titleAttentionMessage = "";
3544
+ let titleAttentionRequestId = null;
3545
+ let titleAttentionRequestKind = null;
3542
3546
 
3543
3547
  function parseFiniteNumber(value) {
3544
3548
  if (value == null || value === "") return null;
@@ -3899,12 +3903,63 @@ ${cssVarsBlock}
3899
3903
  return changed;
3900
3904
  }
3901
3905
 
3906
+ function isTitleAttentionRequestKind(kind) {
3907
+ return kind === "annotation" || kind === "critique" || kind === "direct";
3908
+ }
3909
+
3910
+ function armTitleAttentionForRequest(requestId, kind) {
3911
+ if (typeof requestId !== "string" || !isTitleAttentionRequestKind(kind)) {
3912
+ titleAttentionRequestId = null;
3913
+ titleAttentionRequestKind = null;
3914
+ return;
3915
+ }
3916
+ titleAttentionRequestId = requestId;
3917
+ titleAttentionRequestKind = kind;
3918
+ }
3919
+
3920
+ function clearArmedTitleAttention(requestId) {
3921
+ if (typeof requestId === "string" && titleAttentionRequestId && requestId !== titleAttentionRequestId) {
3922
+ return;
3923
+ }
3924
+ titleAttentionRequestId = null;
3925
+ titleAttentionRequestKind = null;
3926
+ }
3927
+
3928
+ function clearTitleAttention() {
3929
+ if (!titleAttentionMessage) return;
3930
+ titleAttentionMessage = "";
3931
+ updateDocumentTitle();
3932
+ }
3933
+
3934
+ function shouldShowTitleAttention() {
3935
+ const focused = typeof document.hasFocus === "function" ? document.hasFocus() : windowHasFocus;
3936
+ return Boolean(document.hidden) || !focused;
3937
+ }
3938
+
3939
+ function getTitleAttentionMessage(kind) {
3940
+ if (kind === "critique") return "● Critique ready";
3941
+ if (kind === "direct") return "● Response ready";
3942
+ return "● Reply ready";
3943
+ }
3944
+
3945
+ function maybeShowTitleAttentionForCompletedRequest(requestId, kind) {
3946
+ const matchedRequest = typeof requestId === "string" && titleAttentionRequestId && requestId === titleAttentionRequestId;
3947
+ const completedKind = isTitleAttentionRequestKind(kind) ? kind : titleAttentionRequestKind;
3948
+ clearArmedTitleAttention(requestId);
3949
+ if (!matchedRequest || !completedKind || !shouldShowTitleAttention()) {
3950
+ return;
3951
+ }
3952
+ titleAttentionMessage = getTitleAttentionMessage(completedKind);
3953
+ updateDocumentTitle();
3954
+ }
3955
+
3902
3956
  function updateDocumentTitle() {
3903
3957
  const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
3904
3958
  const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
3905
3959
  const titleParts = ["pi Studio"];
3906
3960
  if (terminalText && terminalText !== "unknown") titleParts.push(terminalText);
3907
3961
  if (modelText && modelText !== "none") titleParts.push(modelText);
3962
+ if (titleAttentionMessage) titleParts.unshift(titleAttentionMessage);
3908
3963
  document.title = titleParts.join(" · ");
3909
3964
  }
3910
3965
 
@@ -3998,6 +4053,24 @@ ${cssVarsBlock}
3998
4053
 
3999
4054
  renderStatus();
4000
4055
 
4056
+ window.addEventListener("focus", () => {
4057
+ windowHasFocus = true;
4058
+ clearTitleAttention();
4059
+ });
4060
+
4061
+ window.addEventListener("blur", () => {
4062
+ windowHasFocus = false;
4063
+ });
4064
+
4065
+ document.addEventListener("visibilitychange", () => {
4066
+ if (!document.hidden) {
4067
+ windowHasFocus = typeof document.hasFocus === "function" ? document.hasFocus() : windowHasFocus;
4068
+ if (windowHasFocus) {
4069
+ clearTitleAttention();
4070
+ }
4071
+ }
4072
+ });
4073
+
4001
4074
  function updateSourceBadge() {
4002
4075
  const label = sourceState && sourceState.label ? sourceState.label : "blank";
4003
4076
  sourceBadgeEl.textContent = "Editor origin: " + label;
@@ -5810,8 +5883,10 @@ ${cssVarsBlock}
5810
5883
  setStatus("No matching Studio request is running.", "warning");
5811
5884
  return false;
5812
5885
  }
5813
- const sent = sendMessage({ type: "cancel_request", requestId: pendingRequestId });
5886
+ const requestId = pendingRequestId;
5887
+ const sent = sendMessage({ type: "cancel_request", requestId });
5814
5888
  if (!sent) return false;
5889
+ clearArmedTitleAttention(requestId);
5815
5890
  setStatus("Stopping request…", "warning");
5816
5891
  return true;
5817
5892
  }
@@ -6119,6 +6194,7 @@ ${cssVarsBlock}
6119
6194
  return;
6120
6195
  }
6121
6196
 
6197
+ const completedRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
6122
6198
  const responseKind =
6123
6199
  typeof message.kind === "string"
6124
6200
  ? message.kind
@@ -6151,6 +6227,7 @@ ${cssVarsBlock}
6151
6227
  } else {
6152
6228
  setStatus("Response ready.", "success");
6153
6229
  }
6230
+ maybeShowTitleAttentionForCompletedRequest(completedRequestId, responseKind);
6154
6231
  return;
6155
6232
  }
6156
6233
 
@@ -6352,6 +6429,9 @@ ${cssVarsBlock}
6352
6429
  pendingRequestId = null;
6353
6430
  pendingKind = null;
6354
6431
  }
6432
+ if (typeof message.requestId === "string") {
6433
+ clearArmedTitleAttention(message.requestId);
6434
+ }
6355
6435
  stickyStudioKind = null;
6356
6436
  setBusy(false);
6357
6437
  setWsState("Ready");
@@ -6367,6 +6447,9 @@ ${cssVarsBlock}
6367
6447
  pendingRequestId = null;
6368
6448
  pendingKind = null;
6369
6449
  }
6450
+ if (typeof message.requestId === "string") {
6451
+ clearArmedTitleAttention(message.requestId);
6452
+ }
6370
6453
  stickyStudioKind = null;
6371
6454
  setBusy(false);
6372
6455
  setWsState("Ready");
@@ -6525,10 +6608,12 @@ ${cssVarsBlock}
6525
6608
  setStatus("Studio is busy.", "warning");
6526
6609
  return null;
6527
6610
  }
6611
+ clearTitleAttention();
6528
6612
  const requestId = makeRequestId();
6529
6613
  pendingRequestId = requestId;
6530
6614
  pendingKind = kind;
6531
6615
  stickyStudioKind = kind;
6616
+ armTitleAttentionForRequest(requestId, kind);
6532
6617
  setBusy(true);
6533
6618
  setWsState("Submitting");
6534
6619
  setStatus(getStudioBusyStatus(kind), "warning");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",