@fiodos/web-core 0.1.9 → 0.1.10

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.
@@ -42,8 +42,11 @@ const CSS = `
42
42
  .fyodos-bubble-row{display:flex;gap:8px;align-items:flex-end;padding:8px 10px;border-top:1px solid rgba(160,180,220,0.18);}
43
43
  .fyodos-bubble-row input{flex:1;border:1px solid rgba(15,23,42,0.12);outline:none;border-radius:10px;padding:9px 11px;font-size:15px;}
44
44
  .fyodos-bubble-row button{appearance:none;border:none;flex-shrink:0;display:flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:32px;padding:0;font-size:17px;font-weight:700;line-height:1;color:#000;cursor:pointer;}
45
- .fyodos-thought{position:absolute;appearance:none;display:inline-block;width:max-content;max-width:min(280px,70vw);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left;line-height:1.25;font-weight:500;cursor:pointer;box-shadow:0 8px 24px rgba(0,0,0,0.22);animation:fyodos-thought-in .18s ease-out;}
45
+ .fyodos-thought{position:absolute;appearance:none;box-sizing:border-box;display:flex;align-items:center;width:max-content;max-width:min(280px,70vw);white-space:nowrap;overflow:hidden;text-align:left;line-height:1.25;font-weight:500;cursor:pointer;box-shadow:0 8px 24px rgba(0,0,0,0.22);animation:fyodos-thought-in .18s ease-out;}
46
46
  @keyframes fyodos-thought-in{from{opacity:0;transform:translateY(4px) scale(0.96)}to{opacity:1;transform:none}}
47
+ @keyframes fyodos-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
48
+ .fyodos-thought-track{display:flex;flex-shrink:0;width:max-content;will-change:transform;}
49
+ .fyodos-thought-track>span{flex-shrink:0;padding-right:48px;white-space:nowrap;}
47
50
  @keyframes fyodos-fade-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
48
51
  @keyframes fyodos-blink{0%,100%{opacity:.25}50%{opacity:.9}}
49
52
  @keyframes fy-spin{to{transform:rotate(360deg)}}
@@ -170,19 +173,32 @@ function mountOrb(controller, options = {}) {
170
173
  let chipEl = null;
171
174
  // Thought bubble: a short, screen-aware CTA beside the orb. Created once and
172
175
  // shown/hidden by syncThoughtBubble; tapping it opens the keyboard.
173
- const thoughtBubbleEl = document.createElement('button');
174
- thoughtBubbleEl.type = 'button';
176
+ const thoughtBubbleEl = document.createElement('div');
175
177
  thoughtBubbleEl.className = 'fyodos-thought';
178
+ thoughtBubbleEl.setAttribute('role', 'button');
179
+ thoughtBubbleEl.tabIndex = 0;
176
180
  thoughtBubbleEl.style.display = 'none';
177
181
  thoughtBubbleEl.addEventListener('click', () => {
178
182
  haptics.trigger('orbTap');
179
183
  controller.primeAudio();
180
184
  if (enableTextInput) {
181
- openTextPanel();
185
+ const prompt = resolveCurrentBubblePrompt();
186
+ openTextPanel(prompt
187
+ ? (0, core_1.bubblePromptToAffirmative)(prompt, {
188
+ locale: controller.config.locale,
189
+ messages: controller.messages,
190
+ })
191
+ : undefined);
182
192
  return;
183
193
  }
184
194
  void controller.toggleListening();
185
195
  });
196
+ thoughtBubbleEl.addEventListener('keydown', (event) => {
197
+ if (event.key === 'Enter' || event.key === ' ') {
198
+ event.preventDefault();
199
+ thoughtBubbleEl.click();
200
+ }
201
+ });
186
202
  anchor.appendChild(thoughtBubbleEl);
187
203
  container.appendChild(root);
188
204
  // ── Position (dashboard position; user drag is SESSION-only, never persisted) ─
@@ -450,9 +466,14 @@ function mountOrb(controller, options = {}) {
450
466
  // STAYS OPEN. This is the unified, reference-matching behaviour.
451
467
  void controller.submitTextTurn(value);
452
468
  }
453
- function openTextPanel() {
469
+ function openTextPanel(seed, autoSend = false) {
454
470
  controller.cancelAll();
471
+ const draft = seed?.trim() ?? '';
472
+ textInput.value = draft;
455
473
  setBubble(true);
474
+ if (autoSend && draft) {
475
+ queueMicrotask(() => submitText());
476
+ }
456
477
  }
457
478
  // ── Keyboard chip (rebuilt when theme changes / visibility toggles) ──────────
458
479
  // Appears beside the ACTIVE orb — whether listening for real (voice granted)
@@ -490,26 +511,62 @@ function mountOrb(controller, options = {}) {
490
511
  function resolveCurrentBubblePrompt() {
491
512
  return (0, core_1.resolveBubblePrompt)((0, core_1.matchRouteIntent)(currentRoute, controller.config.manifest.routes), controller.messages, { locale: controller.config.locale, actions: controller.config.manifest.actions });
492
513
  }
514
+ // Render the CTA on ONE line at its NATURAL size. When the phrase is too long
515
+ // to fit the max width, scroll it (marquee, right-to-left, looping) instead of
516
+ // shrinking the font or truncating — the bubble keeps a constant height.
517
+ function layoutThoughtContent(prompt) {
518
+ const t = bubbleTheme;
519
+ const { padH } = (0, core_1.resolveBubblePadding)(t);
520
+ // Measure the natural (single-copy) text width at the base font size.
521
+ thoughtBubbleEl.style.width = '';
522
+ thoughtBubbleEl.textContent = prompt;
523
+ const measured = thoughtBubbleEl.scrollWidth;
524
+ const viewport = typeof window !== 'undefined' ? window.innerWidth : 280;
525
+ const maxW = Math.min(280, viewport * 0.7);
526
+ const overflow = measured > maxW - t.borderWidth * 2;
527
+ if (!overflow) {
528
+ thoughtBubbleEl.style.width = '';
529
+ return;
530
+ }
531
+ const textWidth = Math.max(0, measured - padH * 2);
532
+ const durationSec = Math.max(4, (textWidth + 48) / 30);
533
+ thoughtBubbleEl.style.width = `${maxW}px`;
534
+ thoughtBubbleEl.textContent = '';
535
+ const track = document.createElement('div');
536
+ track.className = 'fyodos-thought-track';
537
+ track.style.animation = `fyodos-marquee ${durationSec}s linear infinite`;
538
+ const first = document.createElement('span');
539
+ first.textContent = prompt;
540
+ const second = document.createElement('span');
541
+ second.textContent = prompt;
542
+ second.setAttribute('aria-hidden', 'true');
543
+ track.appendChild(first);
544
+ track.appendChild(second);
545
+ thoughtBubbleEl.appendChild(track);
546
+ }
493
547
  function applyThoughtBubbleTheme() {
494
548
  const t = bubbleTheme;
495
- const { fontPx, padV, padH, approxHeight } = (0, core_1.resolveBubblePadding)(t);
496
- const shapeStyle = (0, core_1.resolveBubbleShapeStyle)(t, approxHeight);
549
+ const { fontPx, padH, approxHeight } = (0, core_1.resolveBubblePadding)(t);
550
+ const shapeCss = (0, core_1.bubbleShapeToCss)(t, approxHeight);
497
551
  thoughtBubbleEl.style.fontSize = `${fontPx}px`;
498
- thoughtBubbleEl.style.padding = `${padV}px ${padH}px`;
552
+ thoughtBubbleEl.style.height = `${approxHeight}px`;
553
+ thoughtBubbleEl.style.padding = `0 ${padH}px`;
499
554
  thoughtBubbleEl.style.color = t.textColor;
500
555
  thoughtBubbleEl.style.background = t.backgroundColor;
501
556
  thoughtBubbleEl.style.border = `${t.borderWidth}px solid ${t.borderColor}`;
502
- thoughtBubbleEl.style.borderRadius =
503
- shapeStyle.borderRadius != null ? `${shapeStyle.borderRadius}px` : '';
504
- thoughtBubbleEl.style.borderTopLeftRadius =
505
- shapeStyle.borderTopLeftRadius != null ? `${shapeStyle.borderTopLeftRadius}px` : '';
506
- thoughtBubbleEl.style.borderTopRightRadius =
507
- shapeStyle.borderTopRightRadius != null ? `${shapeStyle.borderTopRightRadius}px` : '';
508
- thoughtBubbleEl.style.borderBottomLeftRadius =
509
- shapeStyle.borderBottomLeftRadius != null ? `${shapeStyle.borderBottomLeftRadius}px` : '';
510
- thoughtBubbleEl.style.borderBottomRightRadius =
511
- shapeStyle.borderBottomRightRadius != null ? `${shapeStyle.borderBottomRightRadius}px` : '';
512
- thoughtBubbleEl.style.clipPath = shapeStyle.clipPath ?? '';
557
+ const setImportant = (prop, value) => {
558
+ if (!value) {
559
+ thoughtBubbleEl.style.removeProperty(prop);
560
+ return;
561
+ }
562
+ thoughtBubbleEl.style.setProperty(prop, value, 'important');
563
+ };
564
+ setImportant('border-radius', shapeCss.borderRadius);
565
+ setImportant('border-top-left-radius', shapeCss.borderTopLeftRadius);
566
+ setImportant('border-top-right-radius', shapeCss.borderTopRightRadius);
567
+ setImportant('border-bottom-left-radius', shapeCss.borderBottomLeftRadius);
568
+ setImportant('border-bottom-right-radius', shapeCss.borderBottomRightRadius);
569
+ setImportant('clip-path', shapeCss.clipPath);
513
570
  }
514
571
  function positionThoughtBubble() {
515
572
  const dep = (0, core_1.resolveOrbDeployment)(effectivePosition());
@@ -539,11 +596,12 @@ function mountOrb(controller, options = {}) {
539
596
  thoughtBubbleEl.style.display = 'none';
540
597
  return;
541
598
  }
542
- thoughtBubbleEl.textContent = prompt;
543
- thoughtBubbleEl.setAttribute('aria-label', ui.orbLabel);
599
+ thoughtBubbleEl.setAttribute('aria-label', prompt);
600
+ // Display before measuring so `scrollWidth` (used to detect overflow) is real.
601
+ thoughtBubbleEl.style.display = 'flex';
544
602
  applyThoughtBubbleTheme();
603
+ layoutThoughtContent(prompt);
545
604
  positionThoughtBubble();
546
- thoughtBubbleEl.style.display = 'block';
547
605
  }
548
606
  // ── Overlays (confirmation + consent) ────────────────────────────────────────
549
607
  let overlayEl = null;
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export declare const SDK_VERSION = "0.1.9";
8
+ export declare const SDK_VERSION = "0.1.10";
9
9
  export declare const SDK_PACKAGE = "@fiodos/web-core";
@@ -8,5 +8,5 @@ exports.SDK_PACKAGE = exports.SDK_VERSION = void 0;
8
8
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
9
9
  * by hand — change package.json `version` and re-run the sync/release script.
10
10
  */
11
- exports.SDK_VERSION = '0.1.9';
11
+ exports.SDK_VERSION = '0.1.10';
12
12
  exports.SDK_PACKAGE = '@fiodos/web-core';
@@ -16,7 +16,7 @@
16
16
  * - CONFIRMATIONS: the same security model (manifest decides; strict phrase /
17
17
  * deliberate tap for payment-grade; loose "yes" never resolves strict).
18
18
  */
19
- import { DEFAULT_ORB_SCREEN_POSITION, createWebOrbHaptics, matchRouteIntent, resolveBubblePrompt, resolveBubbleTheme, resolveBubblePadding, resolveBubbleShapeStyle, resolveOrbAnchorPx, resolveOrbDeployment, snapOrbScreenPositionToSide, viewportPixelsToOrbScreenPosition, } from '@fiodos/core';
19
+ import { DEFAULT_ORB_SCREEN_POSITION, createWebOrbHaptics, matchRouteIntent, resolveBubblePrompt, bubblePromptToAffirmative, resolveBubbleTheme, resolveBubblePadding, bubbleShapeToCss, resolveOrbAnchorPx, resolveOrbDeployment, snapOrbScreenPositionToSide, viewportPixelsToOrbScreenPosition, } from '@fiodos/core';
20
20
  import { buildKeyboardChip, createOrbVisual, DEFAULT_ORB_APPEARANCE, } from './orbView.js';
21
21
  import { watchPublishedConfig } from './publishedConfig.js';
22
22
  const STYLE_ID = 'fyodos-orb-styles';
@@ -39,8 +39,11 @@ const CSS = `
39
39
  .fyodos-bubble-row{display:flex;gap:8px;align-items:flex-end;padding:8px 10px;border-top:1px solid rgba(160,180,220,0.18);}
40
40
  .fyodos-bubble-row input{flex:1;border:1px solid rgba(15,23,42,0.12);outline:none;border-radius:10px;padding:9px 11px;font-size:15px;}
41
41
  .fyodos-bubble-row button{appearance:none;border:none;flex-shrink:0;display:flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:32px;padding:0;font-size:17px;font-weight:700;line-height:1;color:#000;cursor:pointer;}
42
- .fyodos-thought{position:absolute;appearance:none;display:inline-block;width:max-content;max-width:min(280px,70vw);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-align:left;line-height:1.25;font-weight:500;cursor:pointer;box-shadow:0 8px 24px rgba(0,0,0,0.22);animation:fyodos-thought-in .18s ease-out;}
42
+ .fyodos-thought{position:absolute;appearance:none;box-sizing:border-box;display:flex;align-items:center;width:max-content;max-width:min(280px,70vw);white-space:nowrap;overflow:hidden;text-align:left;line-height:1.25;font-weight:500;cursor:pointer;box-shadow:0 8px 24px rgba(0,0,0,0.22);animation:fyodos-thought-in .18s ease-out;}
43
43
  @keyframes fyodos-thought-in{from{opacity:0;transform:translateY(4px) scale(0.96)}to{opacity:1;transform:none}}
44
+ @keyframes fyodos-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
45
+ .fyodos-thought-track{display:flex;flex-shrink:0;width:max-content;will-change:transform;}
46
+ .fyodos-thought-track>span{flex-shrink:0;padding-right:48px;white-space:nowrap;}
44
47
  @keyframes fyodos-fade-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
45
48
  @keyframes fyodos-blink{0%,100%{opacity:.25}50%{opacity:.9}}
46
49
  @keyframes fy-spin{to{transform:rotate(360deg)}}
@@ -167,19 +170,32 @@ export function mountOrb(controller, options = {}) {
167
170
  let chipEl = null;
168
171
  // Thought bubble: a short, screen-aware CTA beside the orb. Created once and
169
172
  // shown/hidden by syncThoughtBubble; tapping it opens the keyboard.
170
- const thoughtBubbleEl = document.createElement('button');
171
- thoughtBubbleEl.type = 'button';
173
+ const thoughtBubbleEl = document.createElement('div');
172
174
  thoughtBubbleEl.className = 'fyodos-thought';
175
+ thoughtBubbleEl.setAttribute('role', 'button');
176
+ thoughtBubbleEl.tabIndex = 0;
173
177
  thoughtBubbleEl.style.display = 'none';
174
178
  thoughtBubbleEl.addEventListener('click', () => {
175
179
  haptics.trigger('orbTap');
176
180
  controller.primeAudio();
177
181
  if (enableTextInput) {
178
- openTextPanel();
182
+ const prompt = resolveCurrentBubblePrompt();
183
+ openTextPanel(prompt
184
+ ? bubblePromptToAffirmative(prompt, {
185
+ locale: controller.config.locale,
186
+ messages: controller.messages,
187
+ })
188
+ : undefined);
179
189
  return;
180
190
  }
181
191
  void controller.toggleListening();
182
192
  });
193
+ thoughtBubbleEl.addEventListener('keydown', (event) => {
194
+ if (event.key === 'Enter' || event.key === ' ') {
195
+ event.preventDefault();
196
+ thoughtBubbleEl.click();
197
+ }
198
+ });
183
199
  anchor.appendChild(thoughtBubbleEl);
184
200
  container.appendChild(root);
185
201
  // ── Position (dashboard position; user drag is SESSION-only, never persisted) ─
@@ -447,9 +463,14 @@ export function mountOrb(controller, options = {}) {
447
463
  // STAYS OPEN. This is the unified, reference-matching behaviour.
448
464
  void controller.submitTextTurn(value);
449
465
  }
450
- function openTextPanel() {
466
+ function openTextPanel(seed, autoSend = false) {
451
467
  controller.cancelAll();
468
+ const draft = seed?.trim() ?? '';
469
+ textInput.value = draft;
452
470
  setBubble(true);
471
+ if (autoSend && draft) {
472
+ queueMicrotask(() => submitText());
473
+ }
453
474
  }
454
475
  // ── Keyboard chip (rebuilt when theme changes / visibility toggles) ──────────
455
476
  // Appears beside the ACTIVE orb — whether listening for real (voice granted)
@@ -487,26 +508,62 @@ export function mountOrb(controller, options = {}) {
487
508
  function resolveCurrentBubblePrompt() {
488
509
  return resolveBubblePrompt(matchRouteIntent(currentRoute, controller.config.manifest.routes), controller.messages, { locale: controller.config.locale, actions: controller.config.manifest.actions });
489
510
  }
511
+ // Render the CTA on ONE line at its NATURAL size. When the phrase is too long
512
+ // to fit the max width, scroll it (marquee, right-to-left, looping) instead of
513
+ // shrinking the font or truncating — the bubble keeps a constant height.
514
+ function layoutThoughtContent(prompt) {
515
+ const t = bubbleTheme;
516
+ const { padH } = resolveBubblePadding(t);
517
+ // Measure the natural (single-copy) text width at the base font size.
518
+ thoughtBubbleEl.style.width = '';
519
+ thoughtBubbleEl.textContent = prompt;
520
+ const measured = thoughtBubbleEl.scrollWidth;
521
+ const viewport = typeof window !== 'undefined' ? window.innerWidth : 280;
522
+ const maxW = Math.min(280, viewport * 0.7);
523
+ const overflow = measured > maxW - t.borderWidth * 2;
524
+ if (!overflow) {
525
+ thoughtBubbleEl.style.width = '';
526
+ return;
527
+ }
528
+ const textWidth = Math.max(0, measured - padH * 2);
529
+ const durationSec = Math.max(4, (textWidth + 48) / 30);
530
+ thoughtBubbleEl.style.width = `${maxW}px`;
531
+ thoughtBubbleEl.textContent = '';
532
+ const track = document.createElement('div');
533
+ track.className = 'fyodos-thought-track';
534
+ track.style.animation = `fyodos-marquee ${durationSec}s linear infinite`;
535
+ const first = document.createElement('span');
536
+ first.textContent = prompt;
537
+ const second = document.createElement('span');
538
+ second.textContent = prompt;
539
+ second.setAttribute('aria-hidden', 'true');
540
+ track.appendChild(first);
541
+ track.appendChild(second);
542
+ thoughtBubbleEl.appendChild(track);
543
+ }
490
544
  function applyThoughtBubbleTheme() {
491
545
  const t = bubbleTheme;
492
- const { fontPx, padV, padH, approxHeight } = resolveBubblePadding(t);
493
- const shapeStyle = resolveBubbleShapeStyle(t, approxHeight);
546
+ const { fontPx, padH, approxHeight } = resolveBubblePadding(t);
547
+ const shapeCss = bubbleShapeToCss(t, approxHeight);
494
548
  thoughtBubbleEl.style.fontSize = `${fontPx}px`;
495
- thoughtBubbleEl.style.padding = `${padV}px ${padH}px`;
549
+ thoughtBubbleEl.style.height = `${approxHeight}px`;
550
+ thoughtBubbleEl.style.padding = `0 ${padH}px`;
496
551
  thoughtBubbleEl.style.color = t.textColor;
497
552
  thoughtBubbleEl.style.background = t.backgroundColor;
498
553
  thoughtBubbleEl.style.border = `${t.borderWidth}px solid ${t.borderColor}`;
499
- thoughtBubbleEl.style.borderRadius =
500
- shapeStyle.borderRadius != null ? `${shapeStyle.borderRadius}px` : '';
501
- thoughtBubbleEl.style.borderTopLeftRadius =
502
- shapeStyle.borderTopLeftRadius != null ? `${shapeStyle.borderTopLeftRadius}px` : '';
503
- thoughtBubbleEl.style.borderTopRightRadius =
504
- shapeStyle.borderTopRightRadius != null ? `${shapeStyle.borderTopRightRadius}px` : '';
505
- thoughtBubbleEl.style.borderBottomLeftRadius =
506
- shapeStyle.borderBottomLeftRadius != null ? `${shapeStyle.borderBottomLeftRadius}px` : '';
507
- thoughtBubbleEl.style.borderBottomRightRadius =
508
- shapeStyle.borderBottomRightRadius != null ? `${shapeStyle.borderBottomRightRadius}px` : '';
509
- thoughtBubbleEl.style.clipPath = shapeStyle.clipPath ?? '';
554
+ const setImportant = (prop, value) => {
555
+ if (!value) {
556
+ thoughtBubbleEl.style.removeProperty(prop);
557
+ return;
558
+ }
559
+ thoughtBubbleEl.style.setProperty(prop, value, 'important');
560
+ };
561
+ setImportant('border-radius', shapeCss.borderRadius);
562
+ setImportant('border-top-left-radius', shapeCss.borderTopLeftRadius);
563
+ setImportant('border-top-right-radius', shapeCss.borderTopRightRadius);
564
+ setImportant('border-bottom-left-radius', shapeCss.borderBottomLeftRadius);
565
+ setImportant('border-bottom-right-radius', shapeCss.borderBottomRightRadius);
566
+ setImportant('clip-path', shapeCss.clipPath);
510
567
  }
511
568
  function positionThoughtBubble() {
512
569
  const dep = resolveOrbDeployment(effectivePosition());
@@ -536,11 +593,12 @@ export function mountOrb(controller, options = {}) {
536
593
  thoughtBubbleEl.style.display = 'none';
537
594
  return;
538
595
  }
539
- thoughtBubbleEl.textContent = prompt;
540
- thoughtBubbleEl.setAttribute('aria-label', ui.orbLabel);
596
+ thoughtBubbleEl.setAttribute('aria-label', prompt);
597
+ // Display before measuring so `scrollWidth` (used to detect overflow) is real.
598
+ thoughtBubbleEl.style.display = 'flex';
541
599
  applyThoughtBubbleTheme();
600
+ layoutThoughtContent(prompt);
542
601
  positionThoughtBubble();
543
- thoughtBubbleEl.style.display = 'block';
544
602
  }
545
603
  // ── Overlays (confirmation + consent) ────────────────────────────────────────
546
604
  let overlayEl = null;
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export declare const SDK_VERSION = "0.1.9";
8
+ export declare const SDK_VERSION = "0.1.10";
9
9
  export declare const SDK_PACKAGE = "@fiodos/web-core";
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export const SDK_VERSION = '0.1.9';
8
+ export const SDK_VERSION = '0.1.10';
9
9
  export const SDK_PACKAGE = '@fiodos/web-core';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiodos/web-core",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Framework-agnostic browser layer for the Fiodos agent: a vanilla AgentController (orchestrator), DOM adapters (navigation/voice/storage), HTTP client and decision engine over @fiodos/core. Shared base for @fiodos/vue, @fiodos/svelte and @fiodos/angular (no framework imports).",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {