Animate vertical transitions between client drawers
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: design-harbor-device-ecosystem
|
||||||
|
description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Design Harbor Device Ecosystem
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
[TODO: 1-2 sentences explaining what this skill enables]
|
||||||
|
|
||||||
|
## Structuring This Skill
|
||||||
|
|
||||||
|
[TODO: Choose the structure that best fits this skill's purpose. Common patterns:
|
||||||
|
|
||||||
|
**1. Workflow-Based** (best for sequential processes)
|
||||||
|
- Works well when there are clear step-by-step procedures
|
||||||
|
- Example: DOCX skill with "Workflow Decision Tree" -> "Reading" -> "Creating" -> "Editing"
|
||||||
|
- Structure: ## Overview -> ## Workflow Decision Tree -> ## Step 1 -> ## Step 2...
|
||||||
|
|
||||||
|
**2. Task-Based** (best for tool collections)
|
||||||
|
- Works well when the skill offers different operations/capabilities
|
||||||
|
- Example: PDF skill with "Quick Start" -> "Merge PDFs" -> "Split PDFs" -> "Extract Text"
|
||||||
|
- Structure: ## Overview -> ## Quick Start -> ## Task Category 1 -> ## Task Category 2...
|
||||||
|
|
||||||
|
**3. Reference/Guidelines** (best for standards or specifications)
|
||||||
|
- Works well for brand guidelines, coding standards, or requirements
|
||||||
|
- Example: Brand styling with "Brand Guidelines" -> "Colors" -> "Typography" -> "Features"
|
||||||
|
- Structure: ## Overview -> ## Guidelines -> ## Specifications -> ## Usage...
|
||||||
|
|
||||||
|
**4. Capabilities-Based** (best for integrated systems)
|
||||||
|
- Works well when the skill provides multiple interrelated features
|
||||||
|
- Example: Product Management with "Core Capabilities" -> numbered capability list
|
||||||
|
- Structure: ## Overview -> ## Core Capabilities -> ### 1. Feature -> ### 2. Feature...
|
||||||
|
|
||||||
|
Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations).
|
||||||
|
|
||||||
|
Delete this entire "Structuring This Skill" section when done - it's just guidance.]
|
||||||
|
|
||||||
|
## [TODO: Replace with the first main section based on chosen structure]
|
||||||
|
|
||||||
|
[TODO: Add content here. See examples in existing skills:
|
||||||
|
- Code samples for technical skills
|
||||||
|
- Decision trees for complex workflows
|
||||||
|
- Concrete examples with realistic user requests
|
||||||
|
- References to scripts/templates/references as needed]
|
||||||
|
|
||||||
|
## Resources (optional)
|
||||||
|
|
||||||
|
Create only the resource directories this skill actually needs. Delete this section if no resources are required.
|
||||||
|
|
||||||
|
### scripts/
|
||||||
|
Executable code (Python/Bash/etc.) that can be run directly to perform specific operations.
|
||||||
|
|
||||||
|
**Examples from other skills:**
|
||||||
|
- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation
|
||||||
|
- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing
|
||||||
|
|
||||||
|
**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations.
|
||||||
|
|
||||||
|
**Note:** Scripts may be executed without loading into context, but can still be read by Codex for patching or environment adjustments.
|
||||||
|
|
||||||
|
### references/
|
||||||
|
Documentation and reference material intended to be loaded into context to inform Codex's process and thinking.
|
||||||
|
|
||||||
|
**Examples from other skills:**
|
||||||
|
- Product management: `communication.md`, `context_building.md` - detailed workflow guides
|
||||||
|
- BigQuery: API reference documentation and query examples
|
||||||
|
- Finance: Schema documentation, company policies
|
||||||
|
|
||||||
|
**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Codex should reference while working.
|
||||||
|
|
||||||
|
### assets/
|
||||||
|
Files not intended to be loaded into context, but rather used within the output Codex produces.
|
||||||
|
|
||||||
|
**Examples from other skills:**
|
||||||
|
- Brand styling: PowerPoint template files (.pptx), logo files
|
||||||
|
- Frontend builder: HTML/React boilerplate project directories
|
||||||
|
- Typography: Font files (.ttf, .woff2)
|
||||||
|
|
||||||
|
**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Not every skill requires all three types of resources.**
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Design Harbor Device Ecosystem"
|
||||||
|
short_description: "Design trusted Harbor device integration."
|
||||||
|
default_prompt: "Use -harbor-device-ecosystem to design Harbor Gateway discovery, pairing, and connected-device contracts."
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export const HARBOR_VERSIONS = Object.freeze({
|
export const HARBOR_VERSIONS = Object.freeze({
|
||||||
macClient: '0.25.9',
|
macClient: '0.25.10',
|
||||||
gatewayClient: '0.26.8',
|
gatewayClient: '0.26.9',
|
||||||
gatewayBackend: '0.26.5',
|
gatewayBackend: '0.26.5',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
type CSSProperties,
|
type CSSProperties,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
import {
|
import {
|
||||||
copyText,
|
copyText,
|
||||||
localProxyUrls,
|
localProxyUrls,
|
||||||
@@ -63,6 +64,10 @@ const VERSION_PARTS = [
|
|||||||
['hotfix', 'Hotfix'],
|
['hotfix', 'Hotfix'],
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
const DRAWER_SWITCH_MS = 620;
|
||||||
|
const DRAWER_ORDER = ['subscription', 'instructions', 'devices', 'diagnostics', 'routing'] as const;
|
||||||
|
type DrawerKey = typeof DRAWER_ORDER[number];
|
||||||
|
|
||||||
interface UiError {
|
interface UiError {
|
||||||
context?: string;
|
context?: string;
|
||||||
profileId?: string;
|
profileId?: string;
|
||||||
@@ -442,6 +447,10 @@ export function ClientOverviewPage({
|
|||||||
const [copyAnnouncement, setCopyAnnouncement] = useState<CopyAnnouncement>({ text: '', cycle: 0 });
|
const [copyAnnouncement, setCopyAnnouncement] = useState<CopyAnnouncement>({ text: '', cycle: 0 });
|
||||||
const copyTimersRef = useRef<Partial<Record<CopyKind, ReturnType<typeof setTimeout>>>>({});
|
const copyTimersRef = useRef<Partial<Record<CopyKind, ReturnType<typeof setTimeout>>>>({});
|
||||||
const copyAttemptsRef = useRef<Partial<Record<CopyKind, object>>>({});
|
const copyAttemptsRef = useRef<Partial<Record<CopyKind, object>>>({});
|
||||||
|
const drawerSwitchRef = useRef<{
|
||||||
|
target: DrawerKey;
|
||||||
|
finish: () => void;
|
||||||
|
} | null>(null);
|
||||||
const gatewayAddress = isGateway ? window.location.hostname : '127.0.0.1';
|
const gatewayAddress = isGateway ? window.location.hostname : '127.0.0.1';
|
||||||
const controlHost = window.location.host || `${gatewayAddress}:3456`;
|
const controlHost = window.location.host || `${gatewayAddress}:3456`;
|
||||||
const proxyUrls = localProxyUrls(state?.clientRuntime?.proxyPort, gatewayAddress);
|
const proxyUrls = localProxyUrls(state?.clientRuntime?.proxyPort, gatewayAddress);
|
||||||
@@ -523,6 +532,41 @@ export function ClientOverviewPage({
|
|||||||
controlHost,
|
controlHost,
|
||||||
});
|
});
|
||||||
const diagnosticsAvailable = hasSubscription;
|
const diagnosticsAvailable = hasSubscription;
|
||||||
|
const drawerControls = {
|
||||||
|
subscription: {
|
||||||
|
isOpen: subscriptionFeature.open,
|
||||||
|
panelRef: subscriptionFeature.panelRef,
|
||||||
|
show: subscriptionFeature.toggle,
|
||||||
|
close: subscriptionFeature.close,
|
||||||
|
},
|
||||||
|
instructions: {
|
||||||
|
isOpen: instructionsFeature.isOpen,
|
||||||
|
panelRef: instructionsFeature.panelRef,
|
||||||
|
show: instructionsFeature.toggle,
|
||||||
|
close: instructionsFeature.close,
|
||||||
|
},
|
||||||
|
devices: {
|
||||||
|
isOpen: devicesFeature.isOpen,
|
||||||
|
panelRef: devicesFeature.panelRef,
|
||||||
|
show: devicesFeature.toggle,
|
||||||
|
close: devicesFeature.close,
|
||||||
|
},
|
||||||
|
diagnostics: {
|
||||||
|
isOpen: diagnosticsFeature.isOpen,
|
||||||
|
panelRef: diagnosticsFeature.panelRef,
|
||||||
|
show: diagnosticsFeature.toggle,
|
||||||
|
close: diagnosticsFeature.close,
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
isOpen: routingFeature.isOpen,
|
||||||
|
panelRef: routingFeature.panelRef,
|
||||||
|
show: routingFeature.open,
|
||||||
|
close: routingFeature.forceClose,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const drawerOrder = isGateway
|
||||||
|
? DRAWER_ORDER
|
||||||
|
: DRAWER_ORDER.filter((drawer) => drawer !== 'devices');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setNow(Date.now());
|
setNow(Date.now());
|
||||||
@@ -598,12 +642,77 @@ export function ClientOverviewPage({
|
|||||||
}, 800);
|
}, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openRouting() {
|
function switchDrawer(target: DrawerKey) {
|
||||||
subscriptionFeature.close();
|
const activeSwitch = drawerSwitchRef.current;
|
||||||
instructionsFeature.close();
|
const current = activeSwitch?.target
|
||||||
devicesFeature.close();
|
|| drawerOrder.find((drawer) => drawerControls[drawer].isOpen)
|
||||||
diagnosticsFeature.close();
|
|| null;
|
||||||
routingFeature.open();
|
activeSwitch?.finish();
|
||||||
|
|
||||||
|
if (current === target) {
|
||||||
|
drawerControls[target].close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (current === 'routing' && routingFeature.dirty) {
|
||||||
|
routingFeature.requestClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!current) {
|
||||||
|
drawerControls[target].show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromControl = drawerControls[current];
|
||||||
|
const toControl = drawerControls[target];
|
||||||
|
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||||
|
if (reducedMotion) {
|
||||||
|
flushSync(() => {
|
||||||
|
fromControl.close();
|
||||||
|
toControl.show();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushSync(() => toControl.show());
|
||||||
|
const from = fromControl.panelRef.current;
|
||||||
|
const to = toControl.panelRef.current;
|
||||||
|
if (!from || !to || typeof from.animate !== 'function' || typeof to.animate !== 'function') {
|
||||||
|
fromControl.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
from.inert = true;
|
||||||
|
from.setAttribute('aria-hidden', 'true');
|
||||||
|
const direction = DRAWER_ORDER.indexOf(target) > DRAWER_ORDER.indexOf(current) ? -1 : 1;
|
||||||
|
const options: KeyframeAnimationOptions = {
|
||||||
|
duration: DRAWER_SWITCH_MS,
|
||||||
|
easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
||||||
|
fill: 'both',
|
||||||
|
};
|
||||||
|
const outgoing = from.animate([
|
||||||
|
{ transform: 'translateY(0)', opacity: 1 },
|
||||||
|
{ transform: `translateY(${direction * 100}%)`, opacity: 1 },
|
||||||
|
], options);
|
||||||
|
const incoming = to.animate([
|
||||||
|
{ transform: `translateY(${-direction * 100}%)`, opacity: 1 },
|
||||||
|
{ transform: 'translateY(0)', opacity: 1 },
|
||||||
|
], options);
|
||||||
|
let finished = false;
|
||||||
|
const cancel = () => {
|
||||||
|
from.inert = false;
|
||||||
|
from.removeAttribute('aria-hidden');
|
||||||
|
outgoing.cancel();
|
||||||
|
incoming.cancel();
|
||||||
|
};
|
||||||
|
const finish = () => {
|
||||||
|
if (finished) return;
|
||||||
|
finished = true;
|
||||||
|
flushSync(() => fromControl.close());
|
||||||
|
cancel();
|
||||||
|
if (drawerSwitchRef.current?.target === target) drawerSwitchRef.current = null;
|
||||||
|
};
|
||||||
|
incoming.addEventListener('finish', finish, { once: true });
|
||||||
|
drawerSwitchRef.current = { target, finish };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainIdentity = gatewayDirect
|
const mainIdentity = gatewayDirect
|
||||||
@@ -629,53 +738,33 @@ export function ClientOverviewPage({
|
|||||||
<div className="client-live-region" role="status" aria-live="polite" aria-atomic="true">
|
<div className="client-live-region" role="status" aria-live="polite" aria-atomic="true">
|
||||||
<span key={copyAnnouncement.cycle}>{copyAnnouncement.text}</span>
|
<span key={copyAnnouncement.cycle}>{copyAnnouncement.text}</span>
|
||||||
</div>
|
</div>
|
||||||
{hasSubscription && <nav className="client-secondary-menu" aria-label="Дополнительные меню">
|
{hasSubscription && <nav
|
||||||
|
className="client-secondary-menu"
|
||||||
|
aria-label="Дополнительные меню"
|
||||||
|
onPointerDown={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
<SubscriptionToggle
|
<SubscriptionToggle
|
||||||
feature={subscriptionFeature}
|
feature={subscriptionFeature}
|
||||||
onToggle={() => {
|
onToggle={() => switchDrawer('subscription')}
|
||||||
if (routingFeature.isOpen && !routingFeature.requestClose()) return;
|
|
||||||
instructionsFeature.close();
|
|
||||||
devicesFeature.close();
|
|
||||||
diagnosticsFeature.close();
|
|
||||||
subscriptionFeature.toggle();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<InstructionsToggle
|
<InstructionsToggle
|
||||||
feature={instructionsFeature}
|
feature={instructionsFeature}
|
||||||
onToggle={() => {
|
onToggle={() => switchDrawer('instructions')}
|
||||||
if (routingFeature.isOpen && !routingFeature.requestClose()) return;
|
|
||||||
subscriptionFeature.close();
|
|
||||||
devicesFeature.close();
|
|
||||||
diagnosticsFeature.close();
|
|
||||||
instructionsFeature.toggle();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{isGateway && <DevicesToggle
|
{isGateway && <DevicesToggle
|
||||||
feature={devicesFeature}
|
feature={devicesFeature}
|
||||||
onToggle={() => {
|
onToggle={() => switchDrawer('devices')}
|
||||||
if (routingFeature.isOpen && !routingFeature.requestClose()) return;
|
|
||||||
subscriptionFeature.close();
|
|
||||||
instructionsFeature.close();
|
|
||||||
diagnosticsFeature.close();
|
|
||||||
devicesFeature.toggle();
|
|
||||||
}}
|
|
||||||
/>}
|
/>}
|
||||||
<DiagnosticsToggle
|
<DiagnosticsToggle
|
||||||
feature={diagnosticsFeature}
|
feature={diagnosticsFeature}
|
||||||
onToggle={() => {
|
onToggle={() => switchDrawer('diagnostics')}
|
||||||
if (routingFeature.isOpen && !routingFeature.requestClose()) return;
|
|
||||||
subscriptionFeature.close();
|
|
||||||
instructionsFeature.close();
|
|
||||||
devicesFeature.close();
|
|
||||||
diagnosticsFeature.toggle();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<RoutingToggle
|
<RoutingToggle
|
||||||
feature={routingFeature}
|
feature={routingFeature}
|
||||||
gatewayDirect={gatewayDirect}
|
gatewayDirect={gatewayDirect}
|
||||||
isGateway={isGateway}
|
isGateway={isGateway}
|
||||||
hasSubscription={hasSubscription}
|
hasSubscription={hasSubscription}
|
||||||
onOpen={openRouting}
|
onOpen={() => switchDrawer('routing')}
|
||||||
/>
|
/>
|
||||||
</nav>}
|
</nav>}
|
||||||
<main className={`client-panel${showPower ? '' : ' is-setup'}${!isGateway && hasSubscription ? ' has-subscription' : ''}${isGateway && hasSubscription ? ' is-gateway-home' : ''}`}>
|
<main className={`client-panel${showPower ? '' : ' is-setup'}${!isGateway && hasSubscription ? ' has-subscription' : ''}${isGateway && hasSubscription ? ' is-gateway-home' : ''}`}>
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.client-instructions.client-devices {
|
|
||||||
width: min(580px, 100vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-devices-header {
|
.client-devices-header {
|
||||||
margin-bottom: 28px;
|
margin-bottom: 28px;
|
||||||
}
|
}
|
||||||
@@ -128,8 +124,8 @@
|
|||||||
transform: translate(0, 2px);
|
transform: translate(0, 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-devices-reset-wrap:hover > .client-tooltip,
|
.client-devices-reset-wrap.client-tooltip-anchor:hover > .client-tooltip,
|
||||||
.client-devices-reset-wrap:has(> :focus-visible) > .client-tooltip {
|
.client-devices-reset-wrap.client-tooltip-anchor:has(> :focus-visible) > .client-tooltip {
|
||||||
transform: translate(0, 0);
|
transform: translate(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.client-instructions.client-diagnostics {
|
|
||||||
width: min(580px, 100vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-diagnostics-header {
|
.client-diagnostics-header {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.client-instructions {
|
|
||||||
width: min(470px, 100vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-instruction-list {
|
.client-instruction-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.client-local-rules {
|
|
||||||
width: min(480px, 100vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-local-rules-header {
|
.client-local-rules-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
|||||||
@@ -434,6 +434,7 @@
|
|||||||
|
|
||||||
.client-server-scroll {
|
.client-server-scroll {
|
||||||
max-height: 330px;
|
max-height: 330px;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
|||||||
@@ -38,13 +38,6 @@
|
|||||||
padding: 32px 30px 76px;
|
padding: 32px 30px 76px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 921px) {
|
|
||||||
.client-subscription-drawer {
|
|
||||||
right: 64px;
|
|
||||||
width: min(480px, calc(100vw - 64px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-profiles-header {
|
.client-profiles-header {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -303,7 +296,7 @@
|
|||||||
grid-template-columns: 18px minmax(0, 1fr) auto;
|
grid-template-columns: 18px minmax(0, 1fr) auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 0 8px;
|
padding: 0 8px 0 32px;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--client-text);
|
color: var(--client-text);
|
||||||
|
|||||||
@@ -183,6 +183,8 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0 0 0 auto;
|
inset: 0 0 0 auto;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
|
width: min(580px, 100vw);
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: color-mix(in oklch, var(--client-bg) 99%, var(--client-panel));
|
background: color-mix(in oklch, var(--client-bg) 99%, var(--client-panel));
|
||||||
color: var(--client-text);
|
color: var(--client-text);
|
||||||
@@ -200,6 +202,13 @@
|
|||||||
transition-delay: 0s;
|
transition-delay: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 921px) {
|
||||||
|
.client-drawer {
|
||||||
|
right: 64px;
|
||||||
|
width: min(580px, calc(100vw - 64px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.client-drawer-sheet {
|
.client-drawer-sheet {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -484,10 +493,6 @@
|
|||||||
transition: opacity 650ms cubic-bezier(0.16, 1, 0.3, 1), filter 650ms cubic-bezier(0.16, 1, 0.3, 1);
|
transition: opacity 650ms cubic-bezier(0.16, 1, 0.3, 1), filter 650ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-subscription-drawer {
|
|
||||||
width: min(480px, 100vw);
|
|
||||||
}
|
|
||||||
|
|
||||||
.client-confirmation-popup {
|
.client-confirmation-popup {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
|
|||||||
assert.match(panel, /\{!compact && <TrafficChart/);
|
assert.match(panel, /\{!compact && <TrafficChart/);
|
||||||
assert.doesNotMatch(panel, /Закрепите устройство, чтобы изменить маршрут|Сначала верните маршрут через Gateway/);
|
assert.doesNotMatch(panel, /Закрепите устройство, чтобы изменить маршрут|Сначала верните маршрут через Gateway/);
|
||||||
assert.match(panel, /maxLength=\{64\}[\s\S]*autoFocus/);
|
assert.match(panel, /maxLength=\{64\}[\s\S]*autoFocus/);
|
||||||
assert.match(styles, /\.client-devices \{\s*width: min\(580px, 100vw\)/);
|
assert.match(styles, /\.client-drawer \{[\s\S]*width: min\(580px, 100vw\)/);
|
||||||
assert.match(styles, /\.client-device \{[\s\S]*--client-device-chart-height: 34px;[\s\S]*grid-template-columns: 34px minmax\(0, 1fr\) 112px 34px;[\s\S]*grid-template-rows: 34px var\(--client-device-chart-height\);[\s\S]*padding: 10px 8px/);
|
assert.match(styles, /\.client-device \{[\s\S]*--client-device-chart-height: 34px;[\s\S]*grid-template-columns: 34px minmax\(0, 1fr\) 112px 34px;[\s\S]*grid-template-rows: 34px var\(--client-device-chart-height\);[\s\S]*padding: 10px 8px/);
|
||||||
assert.match(styles, /\.client-device\.is-pinned \{[\s\S]*--client-device-chart-height: 72px/);
|
assert.match(styles, /\.client-device\.is-pinned \{[\s\S]*--client-device-chart-height: 72px/);
|
||||||
assert.match(styles, /\.client-device\.is-deprioritized \{[\s\S]*grid-template-rows: 34px;[\s\S]*padding-block: 6px/);
|
assert.match(styles, /\.client-device\.is-deprioritized \{[\s\S]*grid-template-rows: 34px;[\s\S]*padding-block: 6px/);
|
||||||
@@ -207,6 +207,7 @@ test('Gateway device inventory uses the existing accessible responsive drawer',
|
|||||||
assert.match(styles, /\.client-devices-kicker \{[^}]*flex-wrap: nowrap/);
|
assert.match(styles, /\.client-devices-kicker \{[^}]*flex-wrap: nowrap/);
|
||||||
assert.match(styles, /\.client-devices-reset-wrap \{[^}]*margin-left: auto/);
|
assert.match(styles, /\.client-devices-reset-wrap \{[^}]*margin-left: auto/);
|
||||||
assert.match(styles, /\.client-devices-reset-wrap > \.client-tooltip \{[^}]*white-space: nowrap/);
|
assert.match(styles, /\.client-devices-reset-wrap > \.client-tooltip \{[^}]*white-space: nowrap/);
|
||||||
|
assert.match(styles, /\.client-devices-reset-wrap\.client-tooltip-anchor:hover > \.client-tooltip,[\s\S]*transform: translate\(0, 0\)/);
|
||||||
assert.match(styles, /\.client-devices-reset \{[\s\S]*oklch\(0\.68 0\.15 28\)[\s\S]*\.client-devices-reset:hover:not\(:disabled\) svg[\s\S]*rotate\(-360deg\)/);
|
assert.match(styles, /\.client-devices-reset \{[\s\S]*oklch\(0\.68 0\.15 28\)[\s\S]*\.client-devices-reset:hover:not\(:disabled\) svg[\s\S]*rotate\(-360deg\)/);
|
||||||
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-device-policy svg[\s\S]*\.client-device-alias-trigger[\s\S]*\.client-device-identity-details[\s\S]*\.client-device-identity-copy[\s\S]*\.client-device-traffic-value > span[\s\S]*\.client-device-traffic-breakdown[\s\S]*\.client-device-traffic-lines[\s\S]*\.client-text-morph-value[\s\S]*transition: none;[\s\S]*animation: none/);
|
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-device-policy svg[\s\S]*\.client-device-alias-trigger[\s\S]*\.client-device-identity-details[\s\S]*\.client-device-identity-copy[\s\S]*\.client-device-traffic-value > span[\s\S]*\.client-device-traffic-breakdown[\s\S]*\.client-device-traffic-lines[\s\S]*\.client-text-morph-value[\s\S]*transition: none;[\s\S]*animation: none/);
|
||||||
assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/);
|
assert.match(styles, /\.client-drawer \{[\s\S]*z-index: 50;[\s\S]*box-shadow:/);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ test('device controller preserves Gateway-only polling, monotonic publication an
|
|||||||
assert.match(feature, /resetOpen[\s\S]*if \(resetOpen\) return/);
|
assert.match(feature, /resetOpen[\s\S]*if \(resetOpen\) return/);
|
||||||
assert.match(feature, /resetDeviceTraffic\(snapshot\.revision\)[\s\S]*STATE_CONFLICT[\s\S]*resetDeviceTraffic\(latest\.revision\)/);
|
assert.match(feature, /resetDeviceTraffic\(snapshot\.revision\)[\s\S]*STATE_CONFLICT[\s\S]*resetDeviceTraffic\(latest\.revision\)/);
|
||||||
assert.match(panel, /<ConfirmationDialog[\s\S]*title="Сбросить статистику\?"[\s\S]*confirmLabel="Сбросить"[\s\S]*busy=\{resetting\}/);
|
assert.match(panel, /<ConfirmationDialog[\s\S]*title="Сбросить статистику\?"[\s\S]*confirmLabel="Сбросить"[\s\S]*busy=\{resetting\}/);
|
||||||
assert.match(page, /<DevicesToggle[\s\S]*devicesFeature\.toggle\(\)/);
|
assert.match(page, /<DevicesToggle[\s\S]*onToggle=\{\(\) => switchDrawer\('devices'\)\}/);
|
||||||
assert.match(page, /<GatewayTrafficSummary feature=\{devicesFeature\} now=\{now\}/);
|
assert.match(page, /<GatewayTrafficSummary feature=\{devicesFeature\} now=\{now\}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ test('server rows scroll inside the subscription drawer without moving the main
|
|||||||
const grid = rule('.client-server-grid');
|
const grid = rule('.client-server-grid');
|
||||||
|
|
||||||
assert.match(scroll, /overflow-y:\s*auto/);
|
assert.match(scroll, /overflow-y:\s*auto/);
|
||||||
|
assert.match(scroll, /overflow-x:\s*hidden/);
|
||||||
assert.match(scroll, /scrollbar-width:\s*none/);
|
assert.match(scroll, /scrollbar-width:\s*none/);
|
||||||
assert.match(styles, /\.client-server-scroll::-webkit-scrollbar\s*\{[\s\S]*display:\s*none/);
|
assert.match(styles, /\.client-server-scroll::-webkit-scrollbar\s*\{[\s\S]*display:\s*none/);
|
||||||
assert.match(simpleScroll, /max-height:\s*352px/);
|
assert.match(simpleScroll, /max-height:\s*352px/);
|
||||||
@@ -83,6 +84,7 @@ test('server rows scroll inside the subscription drawer without moving the main
|
|||||||
assert.match(simpleScroll, /scrollbar-width:\s*thin/);
|
assert.match(simpleScroll, /scrollbar-width:\s*thin/);
|
||||||
assert.match(grid, /width:\s*min\(100%, 220px\)/);
|
assert.match(grid, /width:\s*min\(100%, 220px\)/);
|
||||||
assert.match(rule('.client-drawer'), /overflow-y:\s*auto/);
|
assert.match(rule('.client-drawer'), /overflow-y:\s*auto/);
|
||||||
|
assert.match(rule('.client-drawer'), /overflow-x:\s*hidden/);
|
||||||
assert.match(subscription, /client-profile-list[\s\S]*feature\.profiles\.map\(\(profile\) => <ProfileGroup/);
|
assert.match(subscription, /client-profile-list[\s\S]*feature\.profiles\.map\(\(profile\) => <ProfileGroup/);
|
||||||
assert.match(rule('.client-server-toolbar-title'), /grid-column:\s*2/);
|
assert.match(rule('.client-server-toolbar-title'), /grid-column:\s*2/);
|
||||||
assert.match(rule('.client-server-mode-toggle'), /grid-row:\s*2/);
|
assert.match(rule('.client-server-mode-toggle'), /grid-row:\s*2/);
|
||||||
@@ -110,10 +112,7 @@ test('tablet and mobile regions use normal flow with viewport-safe widths', () =
|
|||||||
assert.match(mobile, /\.client-secondary-menu \{[\s\S]*right:\s*8px/);
|
assert.match(mobile, /\.client-secondary-menu \{[\s\S]*right:\s*8px/);
|
||||||
assert.match(mobile, /\.client-power-control > \.harbor-brand \{[\s\S]*scale\(1\.35\)/);
|
assert.match(mobile, /\.client-power-control > \.harbor-brand \{[\s\S]*scale\(1\.35\)/);
|
||||||
assert.match(mobile, /\.client-server-check \{[\s\S]*width:\s*44px/);
|
assert.match(mobile, /\.client-server-check \{[\s\S]*width:\s*44px/);
|
||||||
assert.match(styles, /\.client-instructions\s*\{[\s\S]*width:\s*min\(470px, 100vw\)/);
|
assert.match(rule('.client-drawer'), /width:\s*min\(580px, 100vw\)/);
|
||||||
assert.match(styles, /\.client-local-rules\s*\{[\s\S]*width:\s*min\(480px, 100vw\)/);
|
|
||||||
assert.match(styles, /\.client-instructions\.client-diagnostics\s*\{[\s\S]*width:\s*min\(580px, 100vw\)/);
|
|
||||||
assert.match(styles, /\.client-subscription-drawer\s*\{[\s\S]*width:\s*min\(480px, 100vw\)/);
|
|
||||||
assert.match(styles, /\.client-confirmation-dialog\s*\{[\s\S]*width:\s*min\(430px, calc\(100vw - 48px\)\)/);
|
assert.match(styles, /\.client-confirmation-dialog\s*\{[\s\S]*width:\s*min\(430px, calc\(100vw - 48px\)\)/);
|
||||||
|
|
||||||
for (const viewport of [320, 390, 768]) {
|
for (const viewport of [320, 390, 768]) {
|
||||||
@@ -128,11 +127,11 @@ test('tablet and mobile regions use normal flow with viewport-safe widths', () =
|
|||||||
assert.ok(Math.min(360, desktopOuterColumn) <= desktopOuterColumn, 'details fit the 1440px desktop grid');
|
assert.ok(Math.min(360, desktopOuterColumn) <= desktopOuterColumn, 'details fit the 1440px desktop grid');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('secondary menus share one right rail and both drawers open from the right', () => {
|
test('secondary menus share one right rail and switch equal drawers as a vertical tape', () => {
|
||||||
const disabledRulesLabel = rule('.client-local-rules-toggle:disabled span');
|
const disabledRulesLabel = rule('.client-local-rules-toggle:disabled span');
|
||||||
const zIndex = (selector) => Number(/z-index:\s*(\d+)/.exec(rule(selector))?.[1]);
|
const zIndex = (selector) => Number(/z-index:\s*(\d+)/.exec(rule(selector))?.[1]);
|
||||||
|
|
||||||
assert.match(component, /<nav className="client-secondary-menu" aria-label="Дополнительные меню">/);
|
assert.match(component, /<nav[\s\S]*className="client-secondary-menu"[\s\S]*aria-label="Дополнительные меню"[\s\S]*onPointerDown=\{\(event\) => event\.stopPropagation\(\)\}/);
|
||||||
assert.match(component, /<SubscriptionToggle[\s\S]*<InstructionsToggle[\s\S]*<DevicesToggle/);
|
assert.match(component, /<SubscriptionToggle[\s\S]*<InstructionsToggle[\s\S]*<DevicesToggle/);
|
||||||
assert.match(subscription, /controls="client-subscription-drawer"/);
|
assert.match(subscription, /controls="client-subscription-drawer"/);
|
||||||
assert.match(component, /<InstructionsToggle[\s\S]*<RoutingToggle/);
|
assert.match(component, /<InstructionsToggle[\s\S]*<RoutingToggle/);
|
||||||
@@ -160,6 +159,7 @@ test('secondary menus share one right rail and both drawers open from the right'
|
|||||||
assert.match(styles, /@keyframes client-rail-info-refill[\s\S]*stroke-dashoffset: 1[\s\S]*stroke-dashoffset: 0/);
|
assert.match(styles, /@keyframes client-rail-info-refill[\s\S]*stroke-dashoffset: 1[\s\S]*stroke-dashoffset: 0/);
|
||||||
assert.doesNotMatch(styles, /client-secondary-icon-motion|\.client-local-rules-toggle\.is-open svg\s*\{[^}]*rotate/);
|
assert.doesNotMatch(styles, /client-secondary-icon-motion|\.client-local-rules-toggle\.is-open svg\s*\{[^}]*rotate/);
|
||||||
assert.match(rule('.client-drawer'), /inset:\s*0 0 0 auto/);
|
assert.match(rule('.client-drawer'), /inset:\s*0 0 0 auto/);
|
||||||
|
assert.match(rule('.client-drawer'), /width:\s*min\(580px, 100vw\)/);
|
||||||
assert.match(rule('.client-drawer'), /transform:\s*translateX\(104%\)/);
|
assert.match(rule('.client-drawer'), /transform:\s*translateX\(104%\)/);
|
||||||
assert.match(rule('.client-drawer'), /z-index:\s*50/);
|
assert.match(rule('.client-drawer'), /z-index:\s*50/);
|
||||||
assert.match(rule('.client-drawer'), /box-shadow:/);
|
assert.match(rule('.client-drawer'), /box-shadow:/);
|
||||||
@@ -167,12 +167,16 @@ test('secondary menus share one right rail and both drawers open from the right'
|
|||||||
['.client-confirmation-popup', '.client-secondary-menu', '.client-drawer', '.harbor-versions'].map(zIndex),
|
['.client-confirmation-popup', '.client-secondary-menu', '.client-drawer', '.harbor-versions'].map(zIndex),
|
||||||
[100, 60, 50, 40],
|
[100, 60, 50, 40],
|
||||||
);
|
);
|
||||||
assert.match(rule('.client-instructions'), /width:\s*min\(470px, 100vw\)/);
|
assert.match(styles, /@media \(min-width: 921px\) \{[\s\S]*\.client-drawer \{[\s\S]*right:\s*64px;[\s\S]*width:\s*min\(580px, calc\(100vw - 64px\)\)/);
|
||||||
assert.match(rule('.client-local-rules'), /width:\s*min\(480px, 100vw\)/);
|
|
||||||
assert.match(instructions, /<Drawer[\s\S]*className="client-instructions"/);
|
assert.match(instructions, /<Drawer[\s\S]*className="client-instructions"/);
|
||||||
assert.match(routing, /<Drawer[\s\S]*className="client-local-rules"/);
|
assert.match(routing, /<Drawer[\s\S]*className="client-local-rules"/);
|
||||||
assert.match(subscription, /<Drawer[\s\S]*className="client-subscription-drawer"/);
|
assert.match(subscription, /<Drawer[\s\S]*className="client-subscription-drawer"/);
|
||||||
assert.match(component, /subscriptionFeature\.close\(\)[\s\S]*devicesFeature\.close\(\)[\s\S]*diagnosticsFeature\.close\(\)[\s\S]*instructionsFeature\.toggle\(\)/);
|
assert.match(component, /const DRAWER_ORDER = \['subscription', 'instructions', 'devices', 'diagnostics', 'routing'\]/);
|
||||||
|
assert.match(component, /function switchDrawer\(target: DrawerKey\)[\s\S]*from\.inert = true[\s\S]*translateY\(\$\{direction \* 100\}%\)[\s\S]*translateY\(\$\{-direction \* 100\}%\)/);
|
||||||
|
assert.match(component, /const cancel = \(\) => \{[\s\S]*from\.inert = false;[\s\S]*from\.removeAttribute\('aria-hidden'\)/);
|
||||||
|
assert.match(component, /DRAWER_SWITCH_MS = 620[\s\S]*prefers-reduced-motion: reduce[\s\S]*flushSync/);
|
||||||
|
assert.match(component, /current === 'routing' && routingFeature\.dirty[\s\S]*routingFeature\.requestClose\(\)/);
|
||||||
|
assert.doesNotMatch(component, /instructionsFeature\.close\(\)[\s\S]{0,180}devicesFeature\.close\(\)[\s\S]{0,180}subscriptionFeature\.toggle\(\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('connectivity diagnostics render stable compact tables before the first run', () => {
|
test('connectivity diagnostics render stable compact tables before the first run', () => {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ test('routing lifecycle and Page orchestration keep the existing guards and bloc
|
|||||||
assert.match(feature, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)[\s\S]*removing: true/);
|
assert.match(feature, /matchMedia\('\(prefers-reduced-motion: reduce\)'\)[\s\S]*removing: true/);
|
||||||
assert.match(feature, /document\.startViewTransition\(update\)/);
|
assert.match(feature, /document\.startViewTransition\(update\)/);
|
||||||
assert.match(feature, /operationBlocked\(operations, 'routeRules'\) \|\| rules\.some/);
|
assert.match(feature, /operationBlocked\(operations, 'routeRules'\) \|\| rules\.some/);
|
||||||
assert.match(page, /routingFeature\.isOpen && !routingFeature\.requestClose\(\)/);
|
assert.match(page, /current === 'routing' && routingFeature\.dirty[\s\S]*routingFeature\.requestClose\(\)/);
|
||||||
assert.match(page, /function openRouting\(\) \{[\s\S]*subscriptionFeature\.close\(\)[\s\S]*instructionsFeature\.close\(\)[\s\S]*devicesFeature\.close\(\)[\s\S]*diagnosticsFeature\.close\(\)[\s\S]*routingFeature\.open\(\)/);
|
assert.match(page, /routing:[\s\S]*show: routingFeature\.open,[\s\S]*close: routingFeature\.forceClose/);
|
||||||
assert.match(page, /routingSlot=\{<RoutingPendingStatus[\s\S]*blocked=\{connectionBlocked\}/);
|
assert.match(page, /routingSlot=\{<RoutingPendingStatus[\s\S]*blocked=\{connectionBlocked\}/);
|
||||||
assert.match(page, /<RoutingPanel[\s\S]*InlineError[\s\S]*InlineProgress/);
|
assert.match(page, /<RoutingPanel[\s\S]*InlineError[\s\S]*InlineProgress/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,24 +37,24 @@ const expectedImports = [
|
|||||||
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
||||||
const acceptedLedger = {
|
const acceptedLedger = {
|
||||||
counts: {
|
counts: {
|
||||||
cascadeEdges: 822,
|
cascadeEdges: 818,
|
||||||
customProperties: 103,
|
customProperties: 103,
|
||||||
declarations: 3302,
|
declarations: 3300,
|
||||||
important: 0,
|
important: 0,
|
||||||
keyframes: 56,
|
keyframes: 56,
|
||||||
media: 13,
|
media: 13,
|
||||||
rules: 952,
|
rules: 947,
|
||||||
variableReferences: 808,
|
variableReferences: 808,
|
||||||
},
|
},
|
||||||
hashes: {
|
hashes: {
|
||||||
cascadeEdges: '183435ac9241f0507dd2d82eda73e71eaaee8f8dcbdc0747b83cf8e17e2ce9f7',
|
cascadeEdges: 'e5115e193b09067562309034ecfed8dd35d7435562b581036da00190dfd95650',
|
||||||
customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb',
|
customProperties: 'ef70fb1d9b61b177f1939f811babe1116bee631de26f5eac0aa0d0009ca5a1fb',
|
||||||
declarations: '07ab8e4c442a88368e6727f0665b29a2d27a882d64c38647250a0f4f0c069f6e',
|
declarations: '3e6d3c24e0df278909fd0200470193378866819c1a62a6d0404e74297cab45d7',
|
||||||
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
|
||||||
duplicateSelectors: '0fa66e370695261a2a5c6a189752c07d518d80d68d3e79a3ba4bb34407893c0e',
|
duplicateSelectors: '0fa66e370695261a2a5c6a189752c07d518d80d68d3e79a3ba4bb34407893c0e',
|
||||||
keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce',
|
keyframes: 'd4378751f36eccc87923c12540315462055032a5d34978a0f45ecada0a5cc1ce',
|
||||||
ruleDeclarationSequences: '864dd1f50b04e6e07e30be6e4aa3b72d02d5425dbecf74a28afa6280ce9dfd73',
|
ruleDeclarationSequences: 'ef07abd92d371c865e70dc8e709a396ec46056164035594a8cddda6b0f61a629',
|
||||||
selectors: 'fa5bb2e06dcbb72802e49d67e2312504f27e87e855427498526ea24f143f84c8',
|
selectors: '085df6f266f22b99fc429c089b3142f12fd6f74bfb9b86233c55bf8baa92469c',
|
||||||
variableReferences: '664c093241c4076ca05a9dda06ae88d4e7024c926c4636109790201df8212df4',
|
variableReferences: '664c093241c4076ca05a9dda06ae88d4e7024c926c4636109790201df8212df4',
|
||||||
witnesses: 'ac771c94e7f2c409c3d317de610c2dae5722eb1d6f0b6626a4e1aa986c83fd94',
|
witnesses: 'ac771c94e7f2c409c3d317de610c2dae5722eb1d6f0b6626a4e1aa986c83fd94',
|
||||||
},
|
},
|
||||||
@@ -405,8 +405,8 @@ test('main owns one public stylesheet and the regrouped production CSS is determ
|
|||||||
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
assert.equal((main.match(/import ['"][^'"]+\.css['"]/g) || []).length, 1);
|
||||||
|
|
||||||
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
|
||||||
assert.deepEqual(assets, ['index-DbgBuebh.css']);
|
assert.deepEqual(assets, ['index-DhqvaTQ4.css']);
|
||||||
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
|
||||||
assert.equal(built.byteLength, 127802);
|
assert.equal(built.byteLength, 127639);
|
||||||
assert.equal(sha256(built), '19995a47de1ae634f29b26fb4e484f6d9fb048519cc42ad0a186d11904e2bfb6');
|
assert.equal(sha256(built), 'c5e9ec300671c6660fb23bfaa725a53b165dd08e387362cfac3fbcf709a8d9e9');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ test('feature keeps exact slots, truthy closes and subscription DOM order', () =
|
|||||||
assert.match(feature, /feature\.profiles\.length === 0 && <div className="client-form client-subscription-first-run"/);
|
assert.match(feature, /feature\.profiles\.length === 0 && <div className="client-form client-subscription-first-run"/);
|
||||||
assert.match(page, /<SubscriptionPanel[\s\S]*statusSlot=\{<>[\s\S]*InlineError[\s\S]*renderServerPicker=[\s\S]*<ServerPicker/);
|
assert.match(page, /<SubscriptionPanel[\s\S]*statusSlot=\{<>[\s\S]*InlineError[\s\S]*renderServerPicker=[\s\S]*<ServerPicker/);
|
||||||
assert.doesNotMatch(page, /<InlineProgress[^>]*context="subscription"/);
|
assert.doesNotMatch(page, /<InlineProgress[^>]*context="subscription"/);
|
||||||
assert.match(page, /<SubscriptionToggle[\s\S]*subscriptionFeature\.toggle\(\)/);
|
assert.match(page, /<SubscriptionToggle[\s\S]*onToggle=\{\(\) => switchDrawer\('subscription'\)\}/);
|
||||||
assert.match(page, /subscriptionFeature\.close\(\)[\s\S]*devicesFeature\.close\(\)[\s\S]*diagnosticsFeature\.close\(\)/);
|
assert.match(page, /subscription: \{[\s\S]*show: subscriptionFeature\.toggle,[\s\S]*close: subscriptionFeature\.close/);
|
||||||
assert.doesNotMatch(feature, /setInterval|copyText|client-live-region/);
|
assert.doesNotMatch(feature, /setInterval|copyText|client-live-region/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -129,6 +129,7 @@ test('profiles render as flat accordion groups with scoped controls', () => {
|
|||||||
assert.match(styles, /\.client-profile-body/);
|
assert.match(styles, /\.client-profile-body/);
|
||||||
assert.match(styles, /\.client-profile-body \{[\s\S]*grid-template-rows: 0fr[\s\S]*\.client-profile-body\.is-open \{[\s\S]*grid-template-rows: 1fr/);
|
assert.match(styles, /\.client-profile-body \{[\s\S]*grid-template-rows: 0fr[\s\S]*\.client-profile-body\.is-open \{[\s\S]*grid-template-rows: 1fr/);
|
||||||
assert.match(styles, /\.client-profile-body-inner \{[\s\S]*padding: 0 0 4px 24px/);
|
assert.match(styles, /\.client-profile-body-inner \{[\s\S]*padding: 0 0 4px 24px/);
|
||||||
|
assert.match(styles, /\.client-profile-selected-server \{[\s\S]*padding: 0 8px 0 32px/);
|
||||||
assert.match(styles, /\.client-profile-group\.is-expired \.client-profile-title small \{[\s\S]*color: oklch\(0\.68 0\.14 72\)/);
|
assert.match(styles, /\.client-profile-group\.is-expired \.client-profile-title small \{[\s\S]*color: oklch\(0\.68 0\.14 72\)/);
|
||||||
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-profile-body \*/);
|
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-profile-body \*/);
|
||||||
assert.match(styles, /\.client-profile-refresh\.is-refreshing/);
|
assert.match(styles, /\.client-profile-refresh\.is-refreshing/);
|
||||||
|
|||||||
Reference in New Issue
Block a user