@athenaintel/react 0.40.0 → 0.40.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"run-activity.cjs","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part — so an elapsed timer keyed on it measures\n * \"time since the stream last moved\", not just \"time since the call began\".\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = typeof tail['type'] === 'string' ? (tail['type'] as string) : 'unknown';\n if (type === 'text' || type === 'reasoning') return null;\n const argsText = typeof tail['argsText'] === 'string' ? (tail['argsText'] as string).length : 0;\n const settled = tail['result'] === undefined ? 0 : 1;\n return `${parts.length}:${type}:${argsText}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not an unsettled tool call.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || tail['type'] !== 'tool-call' || tail['result'] !== undefined) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/** Seconds without any streamed change before the row flips to a stall tone. */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis, the elapsed counter once it is due, and the stall tone.\n */\nexport function runActivityLabel(seconds: number, verb: string = RUN_ACTIVITY_DEFAULT_VERB): string {\n if (seconds >= RUN_ACTIVITY_STALL_SECONDS) {\n return `Still working — nothing new for ${seconds}s`;\n }\n if (seconds >= RUN_ACTIVITY_COUNTER_SECONDS) {\n return `${verb}… · ${seconds}s`;\n }\n return `${verb}…`;\n}\n"],"names":["getToolDisplayMeta"],"mappings":";;;AAaO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,OAAO,KAAK,MAAM,MAAM,WAAY,KAAK,MAAM,IAAe;AAC3E,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,QAAM,WAAW,OAAO,KAAK,UAAU,MAAM,WAAY,KAAK,UAAU,EAAa,SAAS;AAC9F,QAAM,UAAU,KAAK,QAAQ,MAAM,SAAY,IAAI;AACnD,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO;AACvD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,KAAK,MAAM,MAAM,eAAe,KAAK,QAAQ,MAAM,OAAW,QAAO;AAClF,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAOA,aAAAA,mBAAmB,QAAQ,EAAE;AACtC;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAGO,MAAM,6BAA6B;AAGnC,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAMO,SAAS,iBAAiB,SAAiB,OAAe,2BAAmC;AAClG,MAAI,WAAW,4BAA4B;AACzC,WAAO,mCAAmC,OAAO;AAAA,EACnD;AACA,MAAI,WAAW,8BAA8B;AAC3C,WAAO,GAAG,IAAI,OAAO,OAAO;AAAA,EAC9B;AACA,SAAO,GAAG,IAAI;AAChB;;;;;;;;"}
1
+ {"version":3,"file":"run-activity.cjs","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\nimport { isStreamingToolPlaceholder } from './super-grouping/activity-timeline';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part. The stall clock itself keys on\n * {@link runProgressKey}, which also moves while a text or reasoning tail grows.\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = tailType(tail);\n if (type === 'text' || type === 'reasoning') return null;\n return tailActivityKey({ tail, type, partCount: parts.length });\n}\n\n/**\n * Characters of streamed text or reasoning per step of {@link runProgressKey}.\n * The model writes a step within a second at any ordinary streaming rate, so\n * the stall clock restarts about as often as words arrive, while the small\n * meta that reads the key re-renders once per step, never once per token. Only\n * a stream slower than one step per stall window (about two characters a\n * second) could read as quiet while it writes.\n */\nexport const RUN_PROGRESS_TEXT_STEP = 64;\n\n/**\n * The stall clock's key for a streaming assistant message: it changes\n * whenever anything streams, so a timer keyed on it counts the seconds\n * nothing has streamed, which is what the stall tone says\n * ({@link isRunActivityStalled}). A part added, a tool call's arguments\n * growing, a result landing, or the tail's text or reasoning growing by\n * another {@link RUN_PROGRESS_TEXT_STEP} characters each move it. A primitive,\n * so a selector returning it re-renders nothing while the stream is still.\n * A tail that is not text or reasoning keys as {@link runActivityKey} does.\n */\nexport function runProgressKey(parts: ReadonlyArray<unknown>): string {\n const tail = tailPart(parts);\n if (!tail) return String(parts.length);\n const type = tailType(tail);\n if (type !== 'text' && type !== 'reasoning') {\n return tailActivityKey({ tail, type, partCount: parts.length });\n }\n const text = tail['text'];\n const step = Math.floor((typeof text === 'string' ? text.length : 0) / RUN_PROGRESS_TEXT_STEP);\n return `${parts.length}:${type}:${step}`;\n}\n\nfunction tailType(tail: Record<string, unknown>): string {\n const type = tail['type'];\n return typeof type === 'string' ? type : 'unknown';\n}\n\n/** What a tool-call (or any other non-text) tail has streamed: its arguments so far, and whether its result is in. */\nfunction tailActivityKey({\n tail,\n type,\n partCount,\n}: {\n tail: Record<string, unknown>;\n type: string;\n partCount: number;\n}): string {\n const argsText = tail['argsText'];\n const argsLength = typeof argsText === 'string' ? argsText.length : 0;\n // A streaming placeholder is not a result: the key moves when the real one replaces it.\n const settled = hasNoResultYet(tail) ? 0 : 1;\n return `${partCount}:${type}:${argsLength}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not a tool call in flight.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || !isTailToolCallRunning(tail)) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\n/**\n * Whether the run is waiting on a tool that is still running: the tail part\n * is a tool call with no result yet, or only the streaming placeholder a\n * long tool (Python execution, a task subagent) publishes while it works. A\n * command, a build or a test suite streams nothing for minutes while it\n * works, so a quiet stream then is work, not a stall.\n */\nexport function isToolCallInFlight(parts: ReadonlyArray<unknown>): boolean {\n const tail = tailPart(parts);\n return tail !== null && isTailToolCallRunning(tail);\n}\n\nfunction isTailToolCallRunning(tail: Record<string, unknown>): boolean {\n return tail['type'] === 'tool-call' && hasNoResultYet(tail);\n}\n\n/** No result yet: none at all, or only the streaming placeholder (`isStreamingToolPlaceholder`). */\nfunction hasNoResultYet(tail: Record<string, unknown>): boolean {\n return tail['result'] === undefined || isStreamingToolPlaceholder({ artifact: tail['artifact'] });\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/**\n * Seconds without any streamed change before the row flips to a stall tone —\n * while the run waits on the model, never while a tool call is in flight\n * ({@link isRunActivityStalled}).\n */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/**\n * Whether the run looks stalled: nothing has streamed for\n * {@link RUN_ACTIVITY_STALL_SECONDS} while it waits on the model. Never while a\n * tool call is in flight: the call's own row says it is running.\n */\nexport function isRunActivityStalled({\n seconds,\n toolCallInFlight,\n}: {\n seconds: number;\n toolCallInFlight: boolean;\n}): boolean {\n return !toolCallInFlight && seconds >= RUN_ACTIVITY_STALL_SECONDS;\n}\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis and the elapsed counter once it is due (`runElapsedLabel`), or the\n * stall wording when the run looks `stalled` ({@link isRunActivityStalled}).\n */\nexport function runActivityLabel({\n seconds,\n verb = RUN_ACTIVITY_DEFAULT_VERB,\n stalled,\n}: {\n seconds: number;\n verb?: string;\n stalled: boolean;\n}): string {\n if (stalled) return `Still working — nothing new for ${seconds}s`;\n const elapsed = runElapsedLabel(seconds);\n return elapsed === null ? `${verb}…` : `${verb}… · ${elapsed}`;\n}\n"],"names":["getToolDisplayMeta","isStreamingToolPlaceholder"],"mappings":";;;;AAcO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,SAAO,gBAAgB,EAAE,MAAM,MAAM,WAAW,MAAM,QAAQ;AAChE;AAUO,MAAM,yBAAyB;AAY/B,SAAS,eAAe,OAAuC;AACpE,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO,OAAO,MAAM,MAAM;AACrC,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,SAAS,UAAU,SAAS,aAAa;AAC3C,WAAO,gBAAgB,EAAE,MAAM,MAAM,WAAW,MAAM,QAAQ;AAAA,EAChE;AACA,QAAM,OAAO,KAAK,MAAM;AACxB,QAAM,OAAO,KAAK,OAAO,OAAO,SAAS,WAAW,KAAK,SAAS,KAAK,sBAAsB;AAC7F,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,IAAI;AACxC;AAEA,SAAS,SAAS,MAAuC;AACvD,QAAM,OAAO,KAAK,MAAM;AACxB,SAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;AAGA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,WAAW,KAAK,UAAU;AAChC,QAAM,aAAa,OAAO,aAAa,WAAW,SAAS,SAAS;AAEpE,QAAM,UAAU,eAAe,IAAI,IAAI,IAAI;AAC3C,SAAO,GAAG,SAAS,IAAI,IAAI,IAAI,UAAU,IAAI,OAAO;AACtD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAI,EAAG,QAAO;AAClD,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAOA,aAAAA,mBAAmB,QAAQ,EAAE;AACtC;AASO,SAAS,mBAAmB,OAAwC;AACzE,QAAM,OAAO,SAAS,KAAK;AAC3B,SAAO,SAAS,QAAQ,sBAAsB,IAAI;AACpD;AAEA,SAAS,sBAAsB,MAAwC;AACrE,SAAO,KAAK,MAAM,MAAM,eAAe,eAAe,IAAI;AAC5D;AAGA,SAAS,eAAe,MAAwC;AAC9D,SAAO,KAAK,QAAQ,MAAM,UAAaC,iBAAAA,2BAA2B,EAAE,UAAU,KAAK,UAAU,GAAG;AAClG;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAOO,MAAM,6BAA6B;AAOnC,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGY;AACV,SAAO,CAAC,oBAAoB,WAAW;AACzC;AAGO,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAOO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAIW;AACT,MAAI,QAAS,QAAO,mCAAmC,OAAO;AAC9D,QAAM,UAAU,gBAAgB,OAAO;AACvC,SAAO,YAAY,OAAO,GAAG,IAAI,MAAM,GAAG,IAAI,OAAO,OAAO;AAC9D;;;;;;;;;;;;"}
@@ -6,18 +6,59 @@
6
6
  * identical. The activity key summarizes the message tail: it is `null` when
7
7
  * no liveness row should show (no parts yet, or a text/reasoning tail that
8
8
  * already animates on its own), and otherwise changes whenever anything
9
- * streams into the tail part — so an elapsed timer keyed on it measures
10
- * "time since the stream last moved", not just "time since the call began".
9
+ * streams into the tail part. The stall clock itself keys on
10
+ * {@link runProgressKey}, which also moves while a text or reasoning tail grows.
11
11
  */
12
12
  export declare function runActivityKey(parts: ReadonlyArray<unknown>): string | null;
13
+ /**
14
+ * Characters of streamed text or reasoning per step of {@link runProgressKey}.
15
+ * The model writes a step within a second at any ordinary streaming rate, so
16
+ * the stall clock restarts about as often as words arrive, while the small
17
+ * meta that reads the key re-renders once per step, never once per token. Only
18
+ * a stream slower than one step per stall window (about two characters a
19
+ * second) could read as quiet while it writes.
20
+ */
21
+ export declare const RUN_PROGRESS_TEXT_STEP = 64;
22
+ /**
23
+ * The stall clock's key for a streaming assistant message: it changes
24
+ * whenever anything streams, so a timer keyed on it counts the seconds
25
+ * nothing has streamed, which is what the stall tone says
26
+ * ({@link isRunActivityStalled}). A part added, a tool call's arguments
27
+ * growing, a result landing, or the tail's text or reasoning growing by
28
+ * another {@link RUN_PROGRESS_TEXT_STEP} characters each move it. A primitive,
29
+ * so a selector returning it re-renders nothing while the stream is still.
30
+ * A tail that is not text or reasoning keys as {@link runActivityKey} does.
31
+ */
32
+ export declare function runProgressKey(parts: ReadonlyArray<unknown>): string;
13
33
  /**
14
34
  * The present-tense verb for what the run is doing right now — the registry's
15
35
  * activity label for a tool call still awaiting its result ("Searching the
16
- * web") — or `null` when the tail is not an unsettled tool call.
36
+ * web") — or `null` when the tail is not a tool call in flight.
17
37
  */
18
38
  export declare function runActivityVerb(parts: ReadonlyArray<unknown>): string | null;
19
- /** Seconds without any streamed change before the row flips to a stall tone. */
39
+ /**
40
+ * Whether the run is waiting on a tool that is still running: the tail part
41
+ * is a tool call with no result yet, or only the streaming placeholder a
42
+ * long tool (Python execution, a task subagent) publishes while it works. A
43
+ * command, a build or a test suite streams nothing for minutes while it
44
+ * works, so a quiet stream then is work, not a stall.
45
+ */
46
+ export declare function isToolCallInFlight(parts: ReadonlyArray<unknown>): boolean;
47
+ /**
48
+ * Seconds without any streamed change before the row flips to a stall tone —
49
+ * while the run waits on the model, never while a tool call is in flight
50
+ * ({@link isRunActivityStalled}).
51
+ */
20
52
  export declare const RUN_ACTIVITY_STALL_SECONDS = 30;
53
+ /**
54
+ * Whether the run looks stalled: nothing has streamed for
55
+ * {@link RUN_ACTIVITY_STALL_SECONDS} while it waits on the model. Never while a
56
+ * tool call is in flight: the call's own row says it is running.
57
+ */
58
+ export declare function isRunActivityStalled({ seconds, toolCallInFlight, }: {
59
+ seconds: number;
60
+ toolCallInFlight: boolean;
61
+ }): boolean;
21
62
  /** Seconds before the row starts showing its elapsed counter. */
22
63
  export declare const RUN_ACTIVITY_COUNTER_SECONDS = 3;
23
64
  /** The default verb when nothing more specific is known. */
@@ -30,6 +71,11 @@ export declare const RUN_ACTIVITY_DEFAULT_VERB = "Thinking";
30
71
  export declare function runElapsedLabel(seconds: number): string | null;
31
72
  /**
32
73
  * The status line for `seconds` since the stream last moved: the verb with an
33
- * ellipsis, the elapsed counter once it is due, and the stall tone.
74
+ * ellipsis and the elapsed counter once it is due (`runElapsedLabel`), or the
75
+ * stall wording when the run looks `stalled` ({@link isRunActivityStalled}).
34
76
  */
35
- export declare function runActivityLabel(seconds: number, verb?: string): string;
77
+ export declare function runActivityLabel({ seconds, verb, stalled, }: {
78
+ seconds: number;
79
+ verb?: string;
80
+ stalled: boolean;
81
+ }): string;
@@ -1,26 +1,67 @@
1
+ import { isStreamingToolPlaceholder } from "./super-grouping/activity-timeline.js";
1
2
  import { getToolDisplayMeta } from "../agent-runtime/src/tool-metadata.js";
2
3
  function runActivityKey(parts) {
3
4
  const tail = tailPart(parts);
4
5
  if (!tail) return null;
5
- const type = typeof tail["type"] === "string" ? tail["type"] : "unknown";
6
+ const type = tailType(tail);
6
7
  if (type === "text" || type === "reasoning") return null;
7
- const argsText = typeof tail["argsText"] === "string" ? tail["argsText"].length : 0;
8
- const settled = tail["result"] === void 0 ? 0 : 1;
9
- return `${parts.length}:${type}:${argsText}:${settled}`;
8
+ return tailActivityKey({ tail, type, partCount: parts.length });
9
+ }
10
+ const RUN_PROGRESS_TEXT_STEP = 64;
11
+ function runProgressKey(parts) {
12
+ const tail = tailPart(parts);
13
+ if (!tail) return String(parts.length);
14
+ const type = tailType(tail);
15
+ if (type !== "text" && type !== "reasoning") {
16
+ return tailActivityKey({ tail, type, partCount: parts.length });
17
+ }
18
+ const text = tail["text"];
19
+ const step = Math.floor((typeof text === "string" ? text.length : 0) / RUN_PROGRESS_TEXT_STEP);
20
+ return `${parts.length}:${type}:${step}`;
21
+ }
22
+ function tailType(tail) {
23
+ const type = tail["type"];
24
+ return typeof type === "string" ? type : "unknown";
25
+ }
26
+ function tailActivityKey({
27
+ tail,
28
+ type,
29
+ partCount
30
+ }) {
31
+ const argsText = tail["argsText"];
32
+ const argsLength = typeof argsText === "string" ? argsText.length : 0;
33
+ const settled = hasNoResultYet(tail) ? 0 : 1;
34
+ return `${partCount}:${type}:${argsLength}:${settled}`;
10
35
  }
11
36
  function runActivityVerb(parts) {
12
37
  const tail = tailPart(parts);
13
- if (!tail || tail["type"] !== "tool-call" || tail["result"] !== void 0) return null;
38
+ if (!tail || !isTailToolCallRunning(tail)) return null;
14
39
  const toolName = tail["toolName"];
15
40
  if (typeof toolName !== "string" || toolName.length === 0) return null;
16
41
  return getToolDisplayMeta(toolName).activeLabel;
17
42
  }
43
+ function isToolCallInFlight(parts) {
44
+ const tail = tailPart(parts);
45
+ return tail !== null && isTailToolCallRunning(tail);
46
+ }
47
+ function isTailToolCallRunning(tail) {
48
+ return tail["type"] === "tool-call" && hasNoResultYet(tail);
49
+ }
50
+ function hasNoResultYet(tail) {
51
+ return tail["result"] === void 0 || isStreamingToolPlaceholder({ artifact: tail["artifact"] });
52
+ }
18
53
  function tailPart(parts) {
19
54
  const last = parts[parts.length - 1];
20
55
  if (last === void 0 || last === null || typeof last !== "object") return null;
21
56
  return last;
22
57
  }
23
58
  const RUN_ACTIVITY_STALL_SECONDS = 30;
59
+ function isRunActivityStalled({
60
+ seconds,
61
+ toolCallInFlight
62
+ }) {
63
+ return !toolCallInFlight && seconds >= RUN_ACTIVITY_STALL_SECONDS;
64
+ }
24
65
  const RUN_ACTIVITY_COUNTER_SECONDS = 3;
25
66
  const RUN_ACTIVITY_DEFAULT_VERB = "Thinking";
26
67
  function runElapsedLabel(seconds) {
@@ -30,22 +71,26 @@ function runElapsedLabel(seconds) {
30
71
  const remainder = seconds % 60;
31
72
  return `${minutes}m ${String(remainder).padStart(2, "0")}s`;
32
73
  }
33
- function runActivityLabel(seconds, verb = RUN_ACTIVITY_DEFAULT_VERB) {
34
- if (seconds >= RUN_ACTIVITY_STALL_SECONDS) {
35
- return `Still working — nothing new for ${seconds}s`;
36
- }
37
- if (seconds >= RUN_ACTIVITY_COUNTER_SECONDS) {
38
- return `${verb}… · ${seconds}s`;
39
- }
40
- return `${verb}…`;
74
+ function runActivityLabel({
75
+ seconds,
76
+ verb = RUN_ACTIVITY_DEFAULT_VERB,
77
+ stalled
78
+ }) {
79
+ if (stalled) return `Still working — nothing new for ${seconds}s`;
80
+ const elapsed = runElapsedLabel(seconds);
81
+ return elapsed === null ? `${verb}…` : `${verb}… · ${elapsed}`;
41
82
  }
42
83
  export {
43
84
  RUN_ACTIVITY_COUNTER_SECONDS,
44
85
  RUN_ACTIVITY_DEFAULT_VERB,
45
86
  RUN_ACTIVITY_STALL_SECONDS,
87
+ RUN_PROGRESS_TEXT_STEP,
88
+ isRunActivityStalled,
89
+ isToolCallInFlight,
46
90
  runActivityKey,
47
91
  runActivityLabel,
48
92
  runActivityVerb,
49
- runElapsedLabel
93
+ runElapsedLabel,
94
+ runProgressKey
50
95
  };
51
96
  //# sourceMappingURL=run-activity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-activity.js","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part — so an elapsed timer keyed on it measures\n * \"time since the stream last moved\", not just \"time since the call began\".\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = typeof tail['type'] === 'string' ? (tail['type'] as string) : 'unknown';\n if (type === 'text' || type === 'reasoning') return null;\n const argsText = typeof tail['argsText'] === 'string' ? (tail['argsText'] as string).length : 0;\n const settled = tail['result'] === undefined ? 0 : 1;\n return `${parts.length}:${type}:${argsText}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not an unsettled tool call.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || tail['type'] !== 'tool-call' || tail['result'] !== undefined) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/** Seconds without any streamed change before the row flips to a stall tone. */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis, the elapsed counter once it is due, and the stall tone.\n */\nexport function runActivityLabel(seconds: number, verb: string = RUN_ACTIVITY_DEFAULT_VERB): string {\n if (seconds >= RUN_ACTIVITY_STALL_SECONDS) {\n return `Still working — nothing new for ${seconds}s`;\n }\n if (seconds >= RUN_ACTIVITY_COUNTER_SECONDS) {\n return `${verb}… · ${seconds}s`;\n }\n return `${verb}…`;\n}\n"],"names":[],"mappings":";AAaO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,OAAO,KAAK,MAAM,MAAM,WAAY,KAAK,MAAM,IAAe;AAC3E,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,QAAM,WAAW,OAAO,KAAK,UAAU,MAAM,WAAY,KAAK,UAAU,EAAa,SAAS;AAC9F,QAAM,UAAU,KAAK,QAAQ,MAAM,SAAY,IAAI;AACnD,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO;AACvD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,KAAK,MAAM,MAAM,eAAe,KAAK,QAAQ,MAAM,OAAW,QAAO;AAClF,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAO,mBAAmB,QAAQ,EAAE;AACtC;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAGO,MAAM,6BAA6B;AAGnC,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAMO,SAAS,iBAAiB,SAAiB,OAAe,2BAAmC;AAClG,MAAI,WAAW,4BAA4B;AACzC,WAAO,mCAAmC,OAAO;AAAA,EACnD;AACA,MAAI,WAAW,8BAA8B;AAC3C,WAAO,GAAG,IAAI,OAAO,OAAO;AAAA,EAC9B;AACA,SAAO,GAAG,IAAI;AAChB;"}
1
+ {"version":3,"file":"run-activity.js","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\nimport { isStreamingToolPlaceholder } from './super-grouping/activity-timeline';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part. The stall clock itself keys on\n * {@link runProgressKey}, which also moves while a text or reasoning tail grows.\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = tailType(tail);\n if (type === 'text' || type === 'reasoning') return null;\n return tailActivityKey({ tail, type, partCount: parts.length });\n}\n\n/**\n * Characters of streamed text or reasoning per step of {@link runProgressKey}.\n * The model writes a step within a second at any ordinary streaming rate, so\n * the stall clock restarts about as often as words arrive, while the small\n * meta that reads the key re-renders once per step, never once per token. Only\n * a stream slower than one step per stall window (about two characters a\n * second) could read as quiet while it writes.\n */\nexport const RUN_PROGRESS_TEXT_STEP = 64;\n\n/**\n * The stall clock's key for a streaming assistant message: it changes\n * whenever anything streams, so a timer keyed on it counts the seconds\n * nothing has streamed, which is what the stall tone says\n * ({@link isRunActivityStalled}). A part added, a tool call's arguments\n * growing, a result landing, or the tail's text or reasoning growing by\n * another {@link RUN_PROGRESS_TEXT_STEP} characters each move it. A primitive,\n * so a selector returning it re-renders nothing while the stream is still.\n * A tail that is not text or reasoning keys as {@link runActivityKey} does.\n */\nexport function runProgressKey(parts: ReadonlyArray<unknown>): string {\n const tail = tailPart(parts);\n if (!tail) return String(parts.length);\n const type = tailType(tail);\n if (type !== 'text' && type !== 'reasoning') {\n return tailActivityKey({ tail, type, partCount: parts.length });\n }\n const text = tail['text'];\n const step = Math.floor((typeof text === 'string' ? text.length : 0) / RUN_PROGRESS_TEXT_STEP);\n return `${parts.length}:${type}:${step}`;\n}\n\nfunction tailType(tail: Record<string, unknown>): string {\n const type = tail['type'];\n return typeof type === 'string' ? type : 'unknown';\n}\n\n/** What a tool-call (or any other non-text) tail has streamed: its arguments so far, and whether its result is in. */\nfunction tailActivityKey({\n tail,\n type,\n partCount,\n}: {\n tail: Record<string, unknown>;\n type: string;\n partCount: number;\n}): string {\n const argsText = tail['argsText'];\n const argsLength = typeof argsText === 'string' ? argsText.length : 0;\n // A streaming placeholder is not a result: the key moves when the real one replaces it.\n const settled = hasNoResultYet(tail) ? 0 : 1;\n return `${partCount}:${type}:${argsLength}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not a tool call in flight.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || !isTailToolCallRunning(tail)) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\n/**\n * Whether the run is waiting on a tool that is still running: the tail part\n * is a tool call with no result yet, or only the streaming placeholder a\n * long tool (Python execution, a task subagent) publishes while it works. A\n * command, a build or a test suite streams nothing for minutes while it\n * works, so a quiet stream then is work, not a stall.\n */\nexport function isToolCallInFlight(parts: ReadonlyArray<unknown>): boolean {\n const tail = tailPart(parts);\n return tail !== null && isTailToolCallRunning(tail);\n}\n\nfunction isTailToolCallRunning(tail: Record<string, unknown>): boolean {\n return tail['type'] === 'tool-call' && hasNoResultYet(tail);\n}\n\n/** No result yet: none at all, or only the streaming placeholder (`isStreamingToolPlaceholder`). */\nfunction hasNoResultYet(tail: Record<string, unknown>): boolean {\n return tail['result'] === undefined || isStreamingToolPlaceholder({ artifact: tail['artifact'] });\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/**\n * Seconds without any streamed change before the row flips to a stall tone —\n * while the run waits on the model, never while a tool call is in flight\n * ({@link isRunActivityStalled}).\n */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/**\n * Whether the run looks stalled: nothing has streamed for\n * {@link RUN_ACTIVITY_STALL_SECONDS} while it waits on the model. Never while a\n * tool call is in flight: the call's own row says it is running.\n */\nexport function isRunActivityStalled({\n seconds,\n toolCallInFlight,\n}: {\n seconds: number;\n toolCallInFlight: boolean;\n}): boolean {\n return !toolCallInFlight && seconds >= RUN_ACTIVITY_STALL_SECONDS;\n}\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis and the elapsed counter once it is due (`runElapsedLabel`), or the\n * stall wording when the run looks `stalled` ({@link isRunActivityStalled}).\n */\nexport function runActivityLabel({\n seconds,\n verb = RUN_ACTIVITY_DEFAULT_VERB,\n stalled,\n}: {\n seconds: number;\n verb?: string;\n stalled: boolean;\n}): string {\n if (stalled) return `Still working — nothing new for ${seconds}s`;\n const elapsed = runElapsedLabel(seconds);\n return elapsed === null ? `${verb}…` : `${verb}… · ${elapsed}`;\n}\n"],"names":[],"mappings":";;AAcO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,SAAO,gBAAgB,EAAE,MAAM,MAAM,WAAW,MAAM,QAAQ;AAChE;AAUO,MAAM,yBAAyB;AAY/B,SAAS,eAAe,OAAuC;AACpE,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO,OAAO,MAAM,MAAM;AACrC,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,SAAS,UAAU,SAAS,aAAa;AAC3C,WAAO,gBAAgB,EAAE,MAAM,MAAM,WAAW,MAAM,QAAQ;AAAA,EAChE;AACA,QAAM,OAAO,KAAK,MAAM;AACxB,QAAM,OAAO,KAAK,OAAO,OAAO,SAAS,WAAW,KAAK,SAAS,KAAK,sBAAsB;AAC7F,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,IAAI;AACxC;AAEA,SAAS,SAAS,MAAuC;AACvD,QAAM,OAAO,KAAK,MAAM;AACxB,SAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;AAGA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,WAAW,KAAK,UAAU;AAChC,QAAM,aAAa,OAAO,aAAa,WAAW,SAAS,SAAS;AAEpE,QAAM,UAAU,eAAe,IAAI,IAAI,IAAI;AAC3C,SAAO,GAAG,SAAS,IAAI,IAAI,IAAI,UAAU,IAAI,OAAO;AACtD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAI,EAAG,QAAO;AAClD,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAO,mBAAmB,QAAQ,EAAE;AACtC;AASO,SAAS,mBAAmB,OAAwC;AACzE,QAAM,OAAO,SAAS,KAAK;AAC3B,SAAO,SAAS,QAAQ,sBAAsB,IAAI;AACpD;AAEA,SAAS,sBAAsB,MAAwC;AACrE,SAAO,KAAK,MAAM,MAAM,eAAe,eAAe,IAAI;AAC5D;AAGA,SAAS,eAAe,MAAwC;AAC9D,SAAO,KAAK,QAAQ,MAAM,UAAa,2BAA2B,EAAE,UAAU,KAAK,UAAU,GAAG;AAClG;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAOO,MAAM,6BAA6B;AAOnC,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGY;AACV,SAAO,CAAC,oBAAoB,WAAW;AACzC;AAGO,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAOO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAIW;AACT,MAAI,QAAS,QAAO,mCAAmC,OAAO;AAC9D,QAAM,UAAU,gBAAgB,OAAO;AACvC,SAAO,YAAY,OAAO,GAAG,IAAI,MAAM,GAAG,IAAI,OAAO,OAAO;AAC9D;"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react = require("react");
4
+ function isActive(doc) {
5
+ if (!doc) return false;
6
+ return doc.visibilityState === "visible" && doc.hasFocus();
7
+ }
8
+ function getServerSnapshot() {
9
+ return false;
10
+ }
11
+ function subscribeTo(doc, onChange) {
12
+ const view = doc == null ? void 0 : doc.defaultView;
13
+ if (!doc || !view) return () => {
14
+ };
15
+ let afterBlur = null;
16
+ const onBlur = () => {
17
+ onChange();
18
+ if (afterBlur !== null) clearTimeout(afterBlur);
19
+ afterBlur = setTimeout(() => {
20
+ afterBlur = null;
21
+ onChange();
22
+ }, 0);
23
+ };
24
+ view.addEventListener("focus", onChange);
25
+ view.addEventListener("blur", onBlur);
26
+ doc.addEventListener("visibilitychange", onChange);
27
+ return () => {
28
+ view.removeEventListener("focus", onChange);
29
+ view.removeEventListener("blur", onBlur);
30
+ doc.removeEventListener("visibilitychange", onChange);
31
+ if (afterBlur !== null) clearTimeout(afterBlur);
32
+ };
33
+ }
34
+ function useDocumentActive(doc) {
35
+ const subscribe = react.useCallback((onChange) => subscribeTo(doc, onChange), [doc]);
36
+ const getSnapshot = react.useCallback(() => isActive(doc), [doc]);
37
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
38
+ }
39
+ exports.useDocumentActive = useDocumentActive;
40
+ //# sourceMappingURL=use-document-active.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-document-active.cjs","sources":["../../src/lib/use-document-active.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\n\nfunction isActive(doc: Document | null): boolean {\n if (!doc) return false;\n return doc.visibilityState === 'visible' && doc.hasFocus();\n}\n\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\nfunction subscribeTo(doc: Document | null, onChange: () => void): () => void {\n const view = doc?.defaultView;\n if (!doc || !view) return () => {};\n let afterBlur: ReturnType<typeof setTimeout> | null = null;\n // `blur` fires before focus has landed. Focus that moved into a frame of\n // this page (a computer's live desktop, an embedded asset) leaves the\n // document focused, so it is read again once the browser has moved it.\n const onBlur = () => {\n onChange();\n if (afterBlur !== null) clearTimeout(afterBlur);\n afterBlur = setTimeout(() => {\n afterBlur = null;\n onChange();\n }, 0);\n };\n view.addEventListener('focus', onChange);\n view.addEventListener('blur', onBlur);\n doc.addEventListener('visibilitychange', onChange);\n return () => {\n view.removeEventListener('focus', onChange);\n view.removeEventListener('blur', onBlur);\n doc.removeEventListener('visibilitychange', onChange);\n if (afterBlur !== null) clearTimeout(afterBlur);\n };\n}\n\n/**\n * Whether `doc` is in front of the person: visible and focused\n * (`hasFocus()`, which stays true while focus is inside a frame of the page).\n * Pass the document a subtree renders into (`useOwnerDocument()`), so a pane\n * popped out into another window reads that window. Re-read on its window's\n * `focus` / `blur` and its `visibilitychange`; nothing polls. `false` on the\n * server and without a document.\n */\nexport function useDocumentActive(doc: Document | null): boolean {\n const subscribe = useCallback((onChange: () => void) => subscribeTo(doc, onChange), [doc]);\n const getSnapshot = useCallback(() => isActive(doc), [doc]);\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"names":["useCallback","useSyncExternalStore"],"mappings":";;;AAEA,SAAS,SAAS,KAA+B;AAC/C,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,oBAAoB,aAAa,IAAI,SAAA;AAClD;AAEA,SAAS,oBAA6B;AACpC,SAAO;AACT;AAEA,SAAS,YAAY,KAAsB,UAAkC;AAC3E,QAAM,OAAO,2BAAK;AAClB,MAAI,CAAC,OAAO,CAAC,aAAa,MAAM;AAAA,EAAC;AACjC,MAAI,YAAkD;AAItD,QAAM,SAAS,MAAM;AACnB,aAAA;AACA,QAAI,cAAc,KAAM,cAAa,SAAS;AAC9C,gBAAY,WAAW,MAAM;AAC3B,kBAAY;AACZ,eAAA;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AACA,OAAK,iBAAiB,SAAS,QAAQ;AACvC,OAAK,iBAAiB,QAAQ,MAAM;AACpC,MAAI,iBAAiB,oBAAoB,QAAQ;AACjD,SAAO,MAAM;AACX,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,SAAK,oBAAoB,QAAQ,MAAM;AACvC,QAAI,oBAAoB,oBAAoB,QAAQ;AACpD,QAAI,cAAc,KAAM,cAAa,SAAS;AAAA,EAChD;AACF;AAUO,SAAS,kBAAkB,KAA+B;AAC/D,QAAM,YAAYA,kBAAY,CAAC,aAAyB,YAAY,KAAK,QAAQ,GAAG,CAAC,GAAG,CAAC;AACzF,QAAM,cAAcA,MAAAA,YAAY,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC;AAC1D,SAAOC,2BAAqB,WAAW,aAAa,iBAAiB;AACvE;;"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether `doc` is in front of the person: visible and focused
3
+ * (`hasFocus()`, which stays true while focus is inside a frame of the page).
4
+ * Pass the document a subtree renders into (`useOwnerDocument()`), so a pane
5
+ * popped out into another window reads that window. Re-read on its window's
6
+ * `focus` / `blur` and its `visibilitychange`; nothing polls. `false` on the
7
+ * server and without a document.
8
+ */
9
+ export declare function useDocumentActive(doc: Document | null): boolean;
@@ -0,0 +1,40 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ function isActive(doc) {
3
+ if (!doc) return false;
4
+ return doc.visibilityState === "visible" && doc.hasFocus();
5
+ }
6
+ function getServerSnapshot() {
7
+ return false;
8
+ }
9
+ function subscribeTo(doc, onChange) {
10
+ const view = doc == null ? void 0 : doc.defaultView;
11
+ if (!doc || !view) return () => {
12
+ };
13
+ let afterBlur = null;
14
+ const onBlur = () => {
15
+ onChange();
16
+ if (afterBlur !== null) clearTimeout(afterBlur);
17
+ afterBlur = setTimeout(() => {
18
+ afterBlur = null;
19
+ onChange();
20
+ }, 0);
21
+ };
22
+ view.addEventListener("focus", onChange);
23
+ view.addEventListener("blur", onBlur);
24
+ doc.addEventListener("visibilitychange", onChange);
25
+ return () => {
26
+ view.removeEventListener("focus", onChange);
27
+ view.removeEventListener("blur", onBlur);
28
+ doc.removeEventListener("visibilitychange", onChange);
29
+ if (afterBlur !== null) clearTimeout(afterBlur);
30
+ };
31
+ }
32
+ function useDocumentActive(doc) {
33
+ const subscribe = useCallback((onChange) => subscribeTo(doc, onChange), [doc]);
34
+ const getSnapshot = useCallback(() => isActive(doc), [doc]);
35
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
36
+ }
37
+ export {
38
+ useDocumentActive
39
+ };
40
+ //# sourceMappingURL=use-document-active.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-document-active.js","sources":["../../src/lib/use-document-active.ts"],"sourcesContent":["import { useCallback, useSyncExternalStore } from 'react';\n\nfunction isActive(doc: Document | null): boolean {\n if (!doc) return false;\n return doc.visibilityState === 'visible' && doc.hasFocus();\n}\n\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\nfunction subscribeTo(doc: Document | null, onChange: () => void): () => void {\n const view = doc?.defaultView;\n if (!doc || !view) return () => {};\n let afterBlur: ReturnType<typeof setTimeout> | null = null;\n // `blur` fires before focus has landed. Focus that moved into a frame of\n // this page (a computer's live desktop, an embedded asset) leaves the\n // document focused, so it is read again once the browser has moved it.\n const onBlur = () => {\n onChange();\n if (afterBlur !== null) clearTimeout(afterBlur);\n afterBlur = setTimeout(() => {\n afterBlur = null;\n onChange();\n }, 0);\n };\n view.addEventListener('focus', onChange);\n view.addEventListener('blur', onBlur);\n doc.addEventListener('visibilitychange', onChange);\n return () => {\n view.removeEventListener('focus', onChange);\n view.removeEventListener('blur', onBlur);\n doc.removeEventListener('visibilitychange', onChange);\n if (afterBlur !== null) clearTimeout(afterBlur);\n };\n}\n\n/**\n * Whether `doc` is in front of the person: visible and focused\n * (`hasFocus()`, which stays true while focus is inside a frame of the page).\n * Pass the document a subtree renders into (`useOwnerDocument()`), so a pane\n * popped out into another window reads that window. Re-read on its window's\n * `focus` / `blur` and its `visibilitychange`; nothing polls. `false` on the\n * server and without a document.\n */\nexport function useDocumentActive(doc: Document | null): boolean {\n const subscribe = useCallback((onChange: () => void) => subscribeTo(doc, onChange), [doc]);\n const getSnapshot = useCallback(() => isActive(doc), [doc]);\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n"],"names":[],"mappings":";AAEA,SAAS,SAAS,KAA+B;AAC/C,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,oBAAoB,aAAa,IAAI,SAAA;AAClD;AAEA,SAAS,oBAA6B;AACpC,SAAO;AACT;AAEA,SAAS,YAAY,KAAsB,UAAkC;AAC3E,QAAM,OAAO,2BAAK;AAClB,MAAI,CAAC,OAAO,CAAC,aAAa,MAAM;AAAA,EAAC;AACjC,MAAI,YAAkD;AAItD,QAAM,SAAS,MAAM;AACnB,aAAA;AACA,QAAI,cAAc,KAAM,cAAa,SAAS;AAC9C,gBAAY,WAAW,MAAM;AAC3B,kBAAY;AACZ,eAAA;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AACA,OAAK,iBAAiB,SAAS,QAAQ;AACvC,OAAK,iBAAiB,QAAQ,MAAM;AACpC,MAAI,iBAAiB,oBAAoB,QAAQ;AACjD,SAAO,MAAM;AACX,SAAK,oBAAoB,SAAS,QAAQ;AAC1C,SAAK,oBAAoB,QAAQ,MAAM;AACvC,QAAI,oBAAoB,oBAAoB,QAAQ;AACpD,QAAI,cAAc,KAAM,cAAa,SAAS;AAAA,EAChD;AACF;AAUO,SAAS,kBAAkB,KAA+B;AAC/D,QAAM,YAAY,YAAY,CAAC,aAAyB,YAAY,KAAK,QAAQ,GAAG,CAAC,GAAG,CAAC;AACzF,QAAM,cAAc,YAAY,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC;AAC1D,SAAO,qBAAqB,WAAW,aAAa,iBAAiB;AACvE;"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const version = "0.40.0";
3
+ const version = "0.40.2";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.40.0";
1
+ const version = "0.40.2";
2
2
  const packageJson = {
3
3
  version
4
4
  };