@reticlehq/browser 3.6.0 → 3.7.0

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 (83) hide show
  1. package/dist/actions/action-report.d.ts +54 -0
  2. package/dist/actions/action-report.js +60 -0
  3. package/dist/actions/actions-dom.d.ts +1 -0
  4. package/dist/actions/actions-dom.js +12 -0
  5. package/dist/actions/actions-press.d.ts +1 -0
  6. package/dist/actions/actions-press.js +25 -1
  7. package/dist/actions/actions.d.ts +1 -37
  8. package/dist/actions/actions.js +24 -66
  9. package/dist/actions/appeared-text.js +3 -0
  10. package/dist/actions/danger-context.d.ts +2 -0
  11. package/dist/actions/danger-context.js +28 -2
  12. package/dist/actions/value-input.d.ts +1 -0
  13. package/dist/actions/value-input.js +11 -0
  14. package/dist/commands/commands.js +4 -0
  15. package/dist/dom/a11y.js +251 -10
  16. package/dist/dom/addressing/coverage-markers.d.ts +6 -0
  17. package/dist/dom/addressing/coverage-markers.js +13 -0
  18. package/dist/dom/dom-ignore.d.ts +2 -0
  19. package/dist/dom/dom-ignore.js +15 -2
  20. package/dist/dom/query.d.ts +1 -1
  21. package/dist/dom/query.js +78 -16
  22. package/dist/dom/render-page.d.ts +12 -0
  23. package/dist/dom/render-page.js +135 -0
  24. package/dist/dom/snapshot.js +55 -7
  25. package/dist/observers/dom.js +32 -16
  26. package/dist/observers/field.js +3 -21
  27. package/dist/observers/network.js +56 -7
  28. package/dist/observers/perf.js +53 -1
  29. package/dist/observers/route.js +6 -5
  30. package/dist/presenter/carousel/carousel.js +2 -7
  31. package/dist/presenter/carousel/panel-slides.d.ts +2 -4
  32. package/dist/presenter/carousel/panel-slides.js +17 -15
  33. package/dist/presenter/chrome/log-memory.d.ts +1 -0
  34. package/dist/presenter/chrome/presenter-log.d.ts +7 -0
  35. package/dist/presenter/chrome/presenter-log.js +48 -8
  36. package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
  37. package/dist/presenter/chrome/presenter-morph.js +34 -0
  38. package/dist/presenter/chrome/presenter-report-copy.d.ts +17 -4
  39. package/dist/presenter/chrome/presenter-report-copy.js +26 -4
  40. package/dist/presenter/hud-telemetry.js +55 -4
  41. package/dist/presenter/presenter-account-styles.js +3 -3
  42. package/dist/presenter/presenter-account.d.ts +27 -5
  43. package/dist/presenter/presenter-account.js +83 -6
  44. package/dist/presenter/presenter-agent-link.d.ts +9 -0
  45. package/dist/presenter/presenter-agent-link.js +62 -0
  46. package/dist/presenter/presenter-chat-views.d.ts +27 -5
  47. package/dist/presenter/presenter-chat-views.js +312 -72
  48. package/dist/presenter/presenter-config.d.ts +2 -0
  49. package/dist/presenter/presenter-config.js +1 -0
  50. package/dist/presenter/presenter-controls.d.ts +0 -1
  51. package/dist/presenter/presenter-controls.js +13 -91
  52. package/dist/presenter/presenter-design-tokens.js +1 -1
  53. package/dist/presenter/presenter-frame.d.ts +1 -1
  54. package/dist/presenter/presenter-frame.js +8 -10
  55. package/dist/presenter/presenter-harness-row.d.ts +67 -0
  56. package/dist/presenter/presenter-harness-row.js +209 -0
  57. package/dist/presenter/presenter-personas.d.ts +35 -0
  58. package/dist/presenter/presenter-personas.js +95 -0
  59. package/dist/presenter/presenter-plan.js +25 -24
  60. package/dist/presenter/presenter-report-styles.js +40 -22
  61. package/dist/presenter/presenter-report.d.ts +2 -1
  62. package/dist/presenter/presenter-report.js +83 -17
  63. package/dist/presenter/presenter-settings-styles.js +8 -4
  64. package/dist/presenter/presenter-settings.d.ts +7 -6
  65. package/dist/presenter/presenter-settings.js +75 -34
  66. package/dist/presenter/presenter-shell-styles.js +12 -3
  67. package/dist/presenter/presenter-shell.d.ts +10 -4
  68. package/dist/presenter/presenter-shell.js +31 -23
  69. package/dist/presenter/presenter-styles.js +0 -2
  70. package/dist/presenter/presenter-workspace.js +10 -3
  71. package/dist/presenter/presenter.d.ts +3 -2
  72. package/dist/presenter/presenter.js +81 -20
  73. package/dist/registry/declared-platform.js +2 -0
  74. package/dist/reticle-inject.js +777 -700
  75. package/dist/reticle.js +36 -2
  76. package/dist/storage-keys.d.ts +3 -2
  77. package/dist/storage-keys.js +7 -3
  78. package/dist/transport/transport.js +5 -3
  79. package/dist/transport/unreachable-message.d.ts +1 -1
  80. package/dist/transport/unreachable-message.js +3 -5
  81. package/package.json +2 -2
  82. package/dist/presenter/carousel/offer-card.d.ts +0 -21
  83. package/dist/presenter/carousel/offer-card.js +0 -56
@@ -1,15 +1,27 @@
1
+ import { CreditKind } from '@reticlehq/core';
2
+ import { TRIAL_OFFER } from '@reticlehq/core/hud';
1
3
  import { hiIconHtml, PRESENTER_ICON_SIZE, PresenterIcon } from './icons/presenter-icons.js';
2
4
  import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
5
+ import { morphHtml } from './chrome/presenter-morph.js';
3
6
  export const ACCOUNT_ROOT_ATTR = 'data-reticle-account';
4
7
  export const ACCOUNT_SIGNIN_ATTR = 'data-reticle-account-signin';
5
8
  export const SYNC_BTN_ATTR = 'data-reticle-sync-now';
6
9
  export const ACCOUNT_TRIGGER_ATTR = 'data-reticle-account-trigger';
7
10
  export const ACCOUNT_MENU_ATTR = 'data-reticle-account-menu';
8
11
  export const ACCOUNT_SIGNOUT_ATTR = 'data-reticle-account-signout';
12
+ export const TEAM_OPEN_ATTR = 'data-reticle-team-open';
13
+ export const TEAM_SAVE_ATTR = 'data-reticle-team-save';
14
+ export const TEAM_SLOT_ATTR = 'data-reticle-team';
15
+ export const TEAM_TEXT = {
16
+ OPEN: 'Open on dashboard ↗',
17
+ OPEN_TITLE: "This project's runs, kept for your team on the Reticle dashboard",
18
+ SAVE_LINK: 'Save runs for your team',
19
+ SAVE_TITLE: `Keep every run, flow and Harness result on a dashboard your team shares. On Reticle, ${TRIAL_OFFER}.`,
20
+ };
9
21
  export const ACCOUNT_TEXT = {
10
22
  SIGNED_OUT: 'Sign in',
11
- SIGNIN_TITLE: 'Sign in through your browser. Also copies `reticle login`, for a terminal',
12
- SIGNIN_COMMAND: 'reticle login',
23
+ SIGNIN_TITLE: 'Sign in through your browser. Also copies `reticle connect`, for a terminal',
24
+ SIGNIN_COMMAND: 'reticle connect',
13
25
  DASHBOARD_TITLE: 'Open this project on the dashboard',
14
26
  SYNC_TITLE: 'Push to the dashboard now, instead of waiting for the next sync',
15
27
  COPIED: 'Copied',
@@ -18,8 +30,8 @@ export const ACCOUNT_TEXT = {
18
30
  TRIGGER_TITLE: 'Your Reticle account',
19
31
  SIGNED_IN_AS: 'Signed in',
20
32
  DASHBOARD_ACTION: 'Open dashboard',
21
- LINK_HINT: 'Not linked — run `reticle link` to keep a record on the dashboard',
22
- LINK_COMMAND: 'reticle link',
33
+ LINK_HINT: 'Not linked — run `reticle connect` to keep a record on the dashboard',
34
+ LINK_COMMAND: 'reticle connect',
23
35
  SIGNOUT_ACTION: 'Copy sign-out command',
24
36
  SIGNOUT_TITLE: 'Copy `reticle logout` to run in your terminal',
25
37
  SIGNOUT_COMMAND: 'reticle logout',
@@ -28,6 +40,11 @@ export const ACCOUNT_TEXT = {
28
40
  HOST_LABEL: 'Host',
29
41
  VERDICTS_LABEL: 'Verdicts',
30
42
  DEFECTS_LABEL: 'Defects found',
43
+ PLAN_LABEL: 'Plan',
44
+ CREDITS_LABEL: 'Credits',
45
+ HARNESS_RUNS_LABEL: 'Harness runs',
46
+ MEMORY_LABEL: 'Memory',
47
+ ON_PLATFORM_LABEL: 'Runs on dashboard',
31
48
  };
32
49
  const ACCOUNT_COPIED_MS = 1200;
33
50
  export function accountInitials(org) {
@@ -70,12 +87,20 @@ function accountMenuHtml(account, details) {
70
87
  `<span class="reticle-account-org">${org.length > 0 ? esc(org) : ACCOUNT_TEXT.SIGNED_IN_AS}</span>` +
71
88
  (account.email !== undefined && account.email.length > 0
72
89
  ? `<span class="reticle-account-email" title="${esc(account.email)}">${esc(account.email)}</span>`
73
- : `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`) +
90
+ :
91
+ org.length > 0
92
+ ? `<span class="reticle-account-email">${ACCOUNT_TEXT.SIGNED_IN_AS}</span>`
93
+ : '') +
74
94
  `</span></div>`;
75
95
  const rows = detailRow(ACCOUNT_TEXT.HOST_LABEL, account.host) +
76
96
  detailRow(ACCOUNT_TEXT.PROJECT_LABEL, details.projectName) +
77
97
  detailRow(ACCOUNT_TEXT.VERDICTS_LABEL, details.verdicts !== undefined ? String(details.verdicts) : undefined) +
78
- detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined);
98
+ detailRow(ACCOUNT_TEXT.DEFECTS_LABEL, details.defects !== undefined ? String(details.defects) : undefined) +
99
+ detailRow(ACCOUNT_TEXT.ON_PLATFORM_LABEL, details.onPlatform) +
100
+ detailRow(ACCOUNT_TEXT.PLAN_LABEL, details.plan) +
101
+ detailRow(ACCOUNT_TEXT.CREDITS_LABEL, details.credits) +
102
+ detailRow(ACCOUNT_TEXT.HARNESS_RUNS_LABEL, details.harnessRuns) +
103
+ detailRow(ACCOUNT_TEXT.MEMORY_LABEL, details.memory);
79
104
  const linked = details.dashboardUrl !== undefined && isSafeDashboardUrl(details.dashboardUrl);
80
105
  const dashboard = linked
81
106
  ? `<a class="reticle-account-action" data-reticle-account-dashboard href="${esc(details.dashboardUrl ?? '')}" target="_blank" rel="noreferrer noopener" title="${ACCOUNT_TEXT.DASHBOARD_TITLE}">${hiIconHtml(PresenterIcon.VIEW, PRESENTER_ICON_SIZE.HELP)}<span>${ACCOUNT_TEXT.DASHBOARD_ACTION}</span></a>`
@@ -88,6 +113,58 @@ function accountMenuHtml(account, details) {
88
113
  `<div class="reticle-account-actions">${dashboard}${signOut}</div>` +
89
114
  `</div>`);
90
115
  }
116
+ const PLAN_NAME = {
117
+ [CreditKind.FREE]: 'Free',
118
+ [CreditKind.TRIAL]: 'Trial',
119
+ [CreditKind.PAID]: 'Pro',
120
+ };
121
+ const plural = (n, one, many) => `${String(n)} ${1 === n ? one : many}`;
122
+ export function platformDetails(config, sync) {
123
+ const plan = config?.plan;
124
+ const onPlatform = sync?.['onPlatform'];
125
+ return {
126
+ ...(plan === undefined
127
+ ? {}
128
+ : {
129
+ plan: [
130
+ PLAN_NAME[plan.kind],
131
+ plan.daysLeft === undefined ? '' : `${plural(plan.daysLeft, 'day', 'days')} left`,
132
+ plan.seats === undefined ? '' : plural(plan.seats, 'seat', 'seats'),
133
+ ]
134
+ .filter((part) => part.length > 0)
135
+ .join(' · '),
136
+ }),
137
+ ...(config?.credits === undefined
138
+ ? {}
139
+ : {
140
+ credits: `${Math.max(0, config.credits.limit - config.credits.used).toLocaleString('en-US')} of ${config.credits.limit.toLocaleString('en-US')} left`,
141
+ }),
142
+ ...(config?.harnessRuns === undefined ? {} : { harnessRuns: String(config.harnessRuns) }),
143
+ ...(config?.memoryEntries === undefined
144
+ ? {}
145
+ : { memory: plural(config.memoryEntries, 'entry', 'entries') }),
146
+ ...('number' === typeof onPlatform ? { onPlatform: String(onPlatform) } : {}),
147
+ };
148
+ }
149
+ export function teamLinkHtml(account, dashboardUrl) {
150
+ if (account === undefined || !account.signedIn)
151
+ return '';
152
+ if (dashboardUrl !== undefined && isSafeDashboardUrl(dashboardUrl))
153
+ return `<a ${TEAM_OPEN_ATTR} class="reticle-team-link" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener" title="${esc(TEAM_TEXT.OPEN_TITLE)}">${TEAM_TEXT.OPEN}</a>`;
154
+ return `<button type="button" ${TEAM_SAVE_ATTR} class="reticle-team-link" title="${esc(TEAM_TEXT.SAVE_TITLE)}"><span>${TEAM_TEXT.SAVE_LINK}</span></button>`;
155
+ }
156
+ export function paintAccountSlot(slot, html) {
157
+ const open = null !== slot.querySelector(`[${ACCOUNT_MENU_ATTR}]:not([hidden])`);
158
+ morphHtml(slot, html);
159
+ if (!open)
160
+ return;
161
+ const menu = slot.querySelector(`[${ACCOUNT_MENU_ATTR}]`);
162
+ if (null === menu)
163
+ return;
164
+ menu.removeAttribute('hidden');
165
+ menu.setAttribute('aria-hidden', 'false');
166
+ slot.querySelector(`[${ACCOUNT_TRIGGER_ATTR}]`)?.setAttribute('aria-expanded', 'true');
167
+ }
91
168
  export function accountCapsuleHtml(account, dashboardUrl, offerSignIn = false) {
92
169
  return accountControlHtml(account, { dashboardUrl }, offerSignIn);
93
170
  }
@@ -0,0 +1,9 @@
1
+ import { type AgentLink } from '@reticlehq/core';
2
+ export declare const AGENT_LINK_HTML: string;
3
+ export declare const AGENT_LINK_CSS = "\n[data-reticle-chat-panel] .reticle-agent-spot{flex:none;display:flex;flex-direction:column;gap:3px;}\n[data-reticle-chat-panel] .reticle-agent-spot[hidden]{display:none;}\n[data-reticle-chat-panel] .reticle-agent-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:30px;padding:0 var(--reticle-hud-space-2);}\n[data-reticle-chat-panel] .reticle-agent-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--reticle-hud-text-faint);}\n[data-reticle-chat-panel] .reticle-agent-row[data-connected=\"1\"] .reticle-agent-dot{background:#4ade80;box-shadow:0 0 0 3px rgba(74,222,128,.15);}\n[data-reticle-chat-panel] .reticle-agent-said{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text-muted);}\n[data-reticle-chat-panel] .reticle-agent-copy:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}\n[data-reticle-chat-panel] .reticle-agent-copy{flex:none;height:22px;padding:0 9px;border:1px solid var(--reticle-hud-border-strong);border-radius:11px;background:transparent;color:var(--reticle-hud-text);font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;}\n[data-reticle-chat-panel] .reticle-agent-copy:hover{border-color:var(--reticle-hud-brand);color:var(--reticle-hud-brand);}\n[data-reticle-chat-panel] .reticle-agent-copy[hidden]{display:none;}\n";
4
+ export declare class AgentLinkView {
5
+ #private;
6
+ mount(root: HTMLElement, signal: AbortSignal): void;
7
+ teardown(): void;
8
+ paint(link: AgentLink | undefined): void;
9
+ }
@@ -0,0 +1,62 @@
1
+ import {} from '@reticlehq/core';
2
+ import { REPORT_TEXT } from './chrome/presenter-report-copy.js';
3
+ const COPIED_FLASH_MS = 1600;
4
+ const TEXT = {
5
+ NONE: 'No coding agent connected',
6
+ named: (names) => `${names} connected`,
7
+ COPY: 'Copy prompt',
8
+ prompt: (url) => `Use Reticle to check ${url} in my running app, then tell me the verdict.`,
9
+ };
10
+ export const AGENT_LINK_HTML = `<div data-reticle-agent-spot class="reticle-agent-spot" hidden>
11
+ <div class="reticle-agent-row"><span data-reticle-agent-dot class="reticle-agent-dot" aria-hidden="true"></span><span data-reticle-agent-said class="reticle-agent-said">${TEXT.NONE}</span><button type="button" data-reticle-agent-copy class="reticle-agent-copy" aria-label="${REPORT_TEXT.COPY_PROMPT}" title="${REPORT_TEXT.COPY_PROMPT}">${TEXT.COPY}</button></div>
12
+ </div>`;
13
+ export const AGENT_LINK_CSS = `
14
+ [data-reticle-chat-panel] .reticle-agent-spot{flex:none;display:flex;flex-direction:column;gap:3px;}
15
+ [data-reticle-chat-panel] .reticle-agent-spot[hidden]{display:none;}
16
+ [data-reticle-chat-panel] .reticle-agent-row{display:flex;align-items:center;gap:8px;min-width:0;min-height:30px;padding:0 var(--reticle-hud-space-2);}
17
+ [data-reticle-chat-panel] .reticle-agent-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--reticle-hud-text-faint);}
18
+ [data-reticle-chat-panel] .reticle-agent-row[data-connected="1"] .reticle-agent-dot{background:#4ade80;box-shadow:0 0 0 3px rgba(74,222,128,.15);}
19
+ [data-reticle-chat-panel] .reticle-agent-said{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);color:var(--reticle-hud-text-muted);}
20
+ [data-reticle-chat-panel] .reticle-agent-copy:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:0;}
21
+ [data-reticle-chat-panel] .reticle-agent-copy{flex:none;height:22px;padding:0 9px;border:1px solid var(--reticle-hud-border-strong);border-radius:11px;background:transparent;color:var(--reticle-hud-text);font:inherit;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap;}
22
+ [data-reticle-chat-panel] .reticle-agent-copy:hover{border-color:var(--reticle-hud-brand);color:var(--reticle-hud-brand);}
23
+ [data-reticle-chat-panel] .reticle-agent-copy[hidden]{display:none;}
24
+ `;
25
+ export class AgentLinkView {
26
+ #root;
27
+ mount(root, signal) {
28
+ this.#root = root;
29
+ root.querySelector('[data-reticle-agent-copy]')?.addEventListener('click', (event) => {
30
+ const button = event.currentTarget;
31
+ void navigator.clipboard
32
+ ?.writeText(TEXT.prompt(location.href))
33
+ .catch(() => undefined)
34
+ .finally(() => {
35
+ if (!(button instanceof HTMLElement))
36
+ return;
37
+ button.textContent = REPORT_TEXT.COPIED;
38
+ window.setTimeout(() => (button.textContent = REPORT_TEXT.COPY_PROMPT), COPIED_FLASH_MS);
39
+ });
40
+ }, { signal });
41
+ }
42
+ teardown() {
43
+ this.#root = undefined;
44
+ }
45
+ paint(link) {
46
+ const root = this.#root;
47
+ const spot = root?.querySelector('[data-reticle-agent-spot]');
48
+ if (root === undefined || null === spot || spot === undefined)
49
+ return;
50
+ spot.hidden = link === undefined;
51
+ if (link === undefined)
52
+ return;
53
+ const connected = 0 < link.agents.length;
54
+ spot.querySelector('.reticle-agent-row')?.setAttribute('data-connected', connected ? '1' : '0');
55
+ const said = spot.querySelector('[data-reticle-agent-said]');
56
+ if (said !== null)
57
+ said.textContent = connected ? TEXT.named(link.agents.join(', ')) : TEXT.NONE;
58
+ const copy = spot.querySelector('[data-reticle-agent-copy]');
59
+ if (copy !== null)
60
+ copy.hidden = connected;
61
+ }
62
+ }
@@ -1,21 +1,43 @@
1
- import { type AccountState, type HarnessConfig } from '@reticlehq/core';
1
+ import { type AccountState, type HarnessConfig, type AgentLink, type HarnessDrive } from '@reticlehq/core';
2
2
  import type { AnnotationItem } from '../review/annotator.js';
3
- export type ChatView = 'activity' | 'flows' | 'annotations';
3
+ import { type HarnessGateView } from './presenter-harness-row.js';
4
+ export type ChatView = 'activity' | 'flows' | 'annotations' | 'harness';
4
5
  export declare function platformBase(config: HarnessConfig | undefined, account: AccountState): string;
5
- export declare const CHAT_VIEWS_HTML = "\n <div data-reticle-harness-spot class=\"reticle-harness-spot\" hidden></div>";
6
+ export interface HudDriveHost {
7
+ run(persona?: string): void;
8
+ stop(): void;
9
+ setHarness(on: boolean): void;
10
+ link(): void;
11
+ }
12
+ export declare const CHAT_VIEWS_HTML: string;
6
13
  export declare const FLOWS_PAGE_HTML: string;
14
+ export declare const NAV_LABEL: {
15
+ readonly LOG: "Log";
16
+ readonly FLOWS: "Flows";
17
+ readonly NOTES: "Notes";
18
+ readonly IMPACT: "Impact";
19
+ readonly HARNESS: "Harness";
20
+ readonly SETTINGS: "Settings";
21
+ readonly HIDE: "Hide";
22
+ };
23
+ export declare const navLabelHtml: (text: string) => string;
7
24
  export declare const CHAT_VIEWS_NAV_HTML: string;
25
+ export declare const HARNESS_PAGE_HTML: string;
8
26
  export declare const ANNOTATIONS_HTML: string;
9
27
  export declare const CHAT_VIEWS_CSS: string;
10
28
  export declare class ChatViews {
11
29
  #private;
12
- constructor(onHarness: (enabled: boolean) => void, onImpact: () => void, onOpenChat: (view: ChatView) => void);
30
+ constructor(onImpact: () => void, onOpenChat: (view: ChatView) => void, drive?: HudDriveHost);
13
31
  mount(root: HTMLElement): void;
14
32
  teardown(): void;
15
33
  open(view: ChatView): void;
16
34
  closePage(): void;
35
+ setProjectId(projectId: string | undefined): void;
36
+ paintAgent(link: AgentLink | undefined): void;
17
37
  paintAccount(account: AccountState | undefined): void;
18
- paintHarness(config: HarnessConfig | undefined): void;
38
+ paintDrive(drive: HarnessDrive | undefined): void;
39
+ paintHarness(config: HarnessConfig | undefined, gate?: HarnessGateView): void;
19
40
  paintImpact(verdicts: number): void;
41
+ setImpactOpen(open: boolean): void;
20
42
  paintAnnotations(items: readonly AnnotationItem[]): void;
21
43
  }