Refine routing rule controls and responsive layout
Build and Deploy Gateway / build-and-push (push) Successful in 35s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-17 16:04:06 +03:00
parent 7c255192b0
commit bc86741397
9 changed files with 227 additions and 109 deletions
+11 -8
View File
@@ -193,20 +193,23 @@ test('secondary menus share one right rail and switch equal drawers as a vertica
assert.doesNotMatch(component, /instructionsFeature\.close\(\)[\s\S]{0,180}devicesFeature\.close\(\)[\s\S]{0,180}subscriptionFeature\.toggle\(\)/);
});
test('routing rules keep two desktop rows and three narrow rows without shrinking drag targets', () => {
assert.match(rule('.client-local-rule'), /grid-template-columns:\s*44px 24px 116px minmax\(0, 1fr\) 28px/);
assert.match(rule('.client-local-rule-meta'), /grid-column:\s*2 \/ -1[\s\S]*grid-row:\s*2/);
test('routing rules stay in one desktop row and reflow without shrinking drag targets', () => {
assert.match(rule('.client-local-rule'), /grid-template-columns:\s*44px 24px 104px minmax\(84px, 1fr\) 34px 16px 28px/);
assert.doesNotMatch(routing, /client-local-rule-meta/);
assert.match(styles, /\.client-local-rules-list \{[\s\S]*position:\s*relative[\s\S]*padding-left:\s*14px/);
assert.match(styles, /\.client-local-rules-list\.has-order-flow::after[\s\S]*clip-path:\s*polygon\(0 0, 100% 0, 50% 100%\)/);
const mobile = /@media \(max-width: 560px\) \{([\s\S]*?)\n\}\s*$/.exec(layoutStyles)?.[1] || '';
assert.match(mobile, /grid-template-columns:\s*44px 44px minmax\(0, 1fr\) 44px/);
assert.match(mobile, /grid-template-columns:\s*44px 44px minmax\(0, 1fr\) 44px 44px/);
assert.match(mobile, /\.client-local-rule input \{[\s\S]*grid-row:\s*2/);
assert.match(mobile, /\.client-local-rule-meta \{[\s\S]*grid-row:\s*3/);
assert.match(mobile, /\.client-rule-outbound \{[\s\S]*grid-column:\s*4;[\s\S]*grid-row:\s*1/);
assert.match(mobile, /\.client-local-rule-status \{[\s\S]*grid-column:\s*5;[\s\S]*grid-row:\s*2/);
assert.match(mobile, /\.client-rule-handle,[\s\S]*width:\s*44px;[\s\S]*height:\s*44px/);
assert.match(mobile, /\.client-rule-type-trigger,[\s\S]*\.client-rule-outbound button \{[\s\S]*height:\s*44px/);
assert.match(mobile, /\.client-rule-type-list button \{[\s\S]*min-height:\s*44px/);
const compact = layoutStyles.slice(layoutStyles.indexOf('@media (max-width: 360px)'));
assert.match(compact, /\.client-local-rule-meta \{[\s\S]*flex-wrap:\s*wrap/);
assert.match(compact, /\.client-rule-outbound \{[\s\S]*width:\s*100%[\s\S]*flex-basis:\s*100%/);
assert.match(compact, /\.client-local-rule-status \{[\s\S]*width:\s*100%[\s\S]*white-space:\s*normal/);
assert.match(compact, /grid-template-columns:\s*44px 44px minmax\(0, 1fr\) 44px/);
assert.match(compact, /\.client-rule-outbound \{[\s\S]*grid-column:\s*2;[\s\S]*grid-row:\s*3/);
assert.match(compact, /\.client-local-rule-status \{[\s\S]*grid-column:\s*4;[\s\S]*grid-row:\s*3/);
});
test('connectivity diagnostics render stable compact tables before the first run', () => {
+11 -3
View File
@@ -99,7 +99,7 @@ test('copy feedback, drawers and Gateway access actions expose complete semantic
assert.match(styles, /@media \(max-width: 560px\)[\s\S]*\.client-rule-handle,[\s\S]*\.client-local-rule-enabled,[\s\S]*\.client-row-delete \{[\s\S]*width: 44px;[\s\S]*height: 44px/);
});
test('ordered rules use one accessible drag handle and a fixed two-target control', () => {
test('ordered rules use one accessible drag handle and a compact icon route control', () => {
assert.match(routing, /className="client-rule-handle"[\s\S]*type="button"[\s\S]*aria-label=\{`Переместить правило, позиция/);
assert.match(routing, /aria-describedby="client-rule-reorder-instructions"[\s\S]*aria-pressed=\{lifted\}/);
assert.match(routing, /onPointerDown=\{\(event\) => feature\.startPointerReorder\(event, rule\._key\)\}/);
@@ -108,8 +108,16 @@ test('ordered rules use one accessible drag handle and a fixed two-target contro
assert.match(routing, /<svg viewBox="0 0 12 28" aria-hidden="true">[\s\S]*<circle[\s\S]*<circle[\s\S]*<circle/);
assert.match(routing, /client-rule-reorder-instructions[\s\S]*стрелки вверх и вниз[\s\S]*Escape отменяет/);
assert.match(routing, /client-rule-reorder-live[\s\S]*aria-live="polite"/);
assert.match(routing, /className="client-rule-outbound" role="group"[\s\S]*\['vpn', 'VPN'\][\s\S]*\['direct', 'Напрямую'\]/);
assert.match(routing, /className="client-rule-outbound client-tooltip-anchor"[\s\S]*className=\{direct \? 'is-direct' : 'is-vpn'\}/);
assert.match(routing, /Маршрут правила \$\{index \+ 1\}: \$\{label\}\. Переключить на/);
assert.match(routing, /M4 12h15M14 7l5 5-5 5[\s\S]*M12 3 19 6v5/);
assert.doesNotMatch(routing, /client-rule-outbound" role="group"/);
assert.match(routing, /className=\{`client-local-rules-list\$\{draftRules\.length > 1 \? ' has-order-flow' : ''\}`\}[\s\S]*aria-label="Правила применяются сверху вниз"/);
assert.match(routing, /className="client-rule-status-dot"/);
assert.doesNotMatch(routing, />\s*[↑↓]\s*</);
assert.match(styles, /\.client-rule-handle \{[\s\S]*width: 44px;[\s\S]*height: 44px;[\s\S]*touch-action: none/);
assert.match(styles, /\.client-rule-outbound \{[\s\S]*width: 128px/);
assert.match(styles, /\.client-rule-outbound \{[\s\S]*width: 34px/);
assert.match(styles, /\.client-rule-outbound svg \{[\s\S]*width: 20px/);
assert.match(styles, /\.client-local-rules-list\.has-order-flow::before[\s\S]*linear-gradient\(to bottom/);
assert.match(styles, /\.client-local-rule \+ \.client-local-rule::before[\s\S]*linear-gradient\(90deg/);
});
+15 -15
View File
@@ -37,26 +37,26 @@ const expectedImports = [
const sha256 = (value) => crypto.createHash('sha256').update(value).digest('hex');
const acceptedLedger = {
counts: {
cascadeEdges: 856,
cascadeEdges: 927,
customProperties: 106,
declarations: 3387,
declarations: 3436,
important: 0,
keyframes: 49,
media: 13,
rules: 959,
variableReferences: 829,
rules: 971,
variableReferences: 834,
},
hashes: {
cascadeEdges: '0aa9651d9c84e6274ee5f64cd3151304b612aff0a06858c3d86d935996a36216',
cascadeEdges: 'ea513313fc4cdfa85a5de74c76bdf06a00df1fc8f71825053b05f06684c7d6df',
customProperties: '06f39794d72566c2b2f8e4a2354866a54dbdceac0a1fdf9ae2619aae9abf2f36',
declarations: '09d0332d509ab6ae1d148a75ef5e931439eab7b29909529c8454382f02778fba',
declarations: '0109c86e26e22898aec17aca2cf952edb88748499735c0fd2fd2163d7dd37dda',
duplicateKeyframes: '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945',
duplicateSelectors: 'abdba98d777bbb19d76443af200ea2cc9018a11e5ef4ae2885ff2f5dd07ef178',
duplicateSelectors: '8982145dba05b33bf1c93b2d54cfbe76305b276ff3c657aa020144016ada5848',
keyframes: '9e78309512ed82b1e9f87c58aeff505dfcdb694fc30c8f54570d01dce13eb51a',
ruleDeclarationSequences: '6e22245aef1e61fe8d56ad064d5e60d02696ec2af5446caacd66311973d5e02c',
selectors: '6487fcc63dbe251f250b79dd1430542e366c598ce93208eebfe51c344dcac38a',
variableReferences: 'f9899b21c75649b687ebd660de6e3781aade3345e1f5bac49b0f6e235f63947d',
witnesses: '87efd05d7a1fd22bc4f421573b41a115a86b2587703266079f9c913e26c323c9',
ruleDeclarationSequences: '60267f6fbf59602e9028a9be4d8b591e1cd3e999eb5b86ab4e129199fd1853ea',
selectors: '9970cb8a580a9c57a3f39f7a801bef08e76a905ba8042deae58ac637b9769781',
variableReferences: '54bf2dffb86a0c9fc42c3d309cada316f446b17d8e54ce3c89b1108adb6e3db6',
witnesses: '0db2c977cfd7cd86209a972e02bcbca9c22722155a88ba789ccf2a395eae4cfa',
},
};
@@ -209,7 +209,7 @@ test('client typography uses the shared semantic scale outside the token owner',
test('accepted stylesheet has pinned declaration, selector, keyframe, variable, and cascade ledgers', () => {
const witnesses = readStyleWitnesses(root);
assert.equal(witnesses.length, 838);
assert.equal(witnesses.length, 845);
assert.equal(witnesses.filter((witness) => witness.unknown || witness.ancestorUnknown).length, 0);
const ledger = createStyleLedger(readStyleSource(root), { witnesses });
assert.deepEqual(ledger.counts, acceptedLedger.counts);
@@ -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);
const assets = fs.readdirSync(path.join(root, 'dist/assets')).filter((file) => file.endsWith('.css'));
assert.deepEqual(assets, ['index-BoF9kamK.css']);
assert.deepEqual(assets, ['index-D1sDzEdb.css']);
const built = fs.readFileSync(path.join(root, 'dist/assets', assets[0]));
assert.equal(built.byteLength, 131216);
assert.equal(sha256(built), '1cab733ed1ae385a808f2afe5666ecc2252b48808d69a20c85e148c5eb60b96f');
assert.equal(built.byteLength, 133160);
assert.equal(sha256(built), '165e6706c69d737a09b9cdde9d8211e94dbea49b3df42c8e5c9d194993e6b618');
});
+1 -1
View File
@@ -32,7 +32,7 @@ test('all repeated client primitive consumers use the shared owners', () => {
.map(({ source }) => source)
.join('\n');
assert.equal((production.match(/<Tooltip\b/g) || []).length, 10);
assert.equal((production.match(/<Tooltip\b/g) || []).length, 12);
assert.equal((production.match(/<CopyButton\b/g) || []).length, 2);
assert.equal((production.match(/<RailAction\b/g) || []).length, 5);
assert.equal((production.match(/<Drawer\b/g) || []).length, 5);