openyida 2026.10.21-2 → 2026.10.22
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.
- package/README.md +3 -1
- package/lib/app/canvas-compile.js +8 -0
- package/lib/app/page-linter.js +10 -0
- package/lib/app/responsive-layout-guard.js +82 -0
- package/lib/core/command-manifest.js +6 -4
- package/lib/core/locales/en.js +34 -1
- package/lib/core/locales/zh.js +34 -1
- package/lib/core/sample.js +3 -0
- package/lib/feedback/feedback.js +2 -1
- package/lib/feedback/github-setup.js +97 -0
- package/lib/feedback/github.js +17 -8
- package/lib/feedback/report.js +35 -15
- package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +1 -1
- package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +1 -1
- package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +1 -1
- package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +36 -24
- package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +1 -1
- package/lib/samples/openyida-scaffold/canvas-responsive-layout.jsx +104 -0
- package/package.json +1 -1
- package/yida-skills/SKILL.md +2 -2
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +5 -1
- package/yida-skills/skills/yida-custom-page/SKILL.md +6 -2
- package/yida-skills/skills/yida-density/SKILL.md +6 -1
- package/yida-skills/skills/yida-design/SKILL.md +1 -1
- package/yida-skills/skills/yida-design/references/responsive-layout.md +69 -22
- package/yida-skills/skills/yida-design/references/task-driven-visual-design.md +2 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +1 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +1 -1
- package/yida-skills/skills/yida-design/workflow/output-design.md +2 -2
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +2 -0
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +10 -10
- package/yida-skills/skills/yida-feedback/SKILL.md +22 -5
- package/yida-skills/skills/yida-feedback/references/github-contribution.md +14 -8
- package/yida-skills/skills/yida-feedback/references/github-setup.md +74 -0
- package/yida-skills/skills/yida-feedback/references/report-input.md +10 -2
- package/yida-skills/skills/yida-feedback/references/user-interaction.md +19 -2
- package/yida-skills/skills/yida-prd/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -446,7 +446,9 @@ openyida feedback report --input summary.json --output-dir feedback-draft-001
|
|
|
446
446
|
|
|
447
447
|
Your AI prepares the summary from observed evidence and selected reproduction screenshots. PRs include before/after test comparisons. Before anything is published, review the final text and images, confirm sensitive information, and remove anything you do not want to share. See the [report input format](yida-skills/skills/yida-feedback/references/report-input.md). Review business information before sharing. With a GitHub account, authorize your AI to submit an Issue or contribute a verified public OpenYida fix through your personal Fork. Without an account, create a GitHub account first. If that is not possible, share the report in the AI开发者群 (AI developer group).
|
|
448
448
|
|
|
449
|
-
|
|
449
|
+
To configure a submission tool, your AI can first run `openyida feedback github setup --json` to check gh and the current account. After you approve the installation plan, it can run `openyida feedback github setup --install --confirm --json`, using existing Homebrew on macOS/Linux or WinGet on Windows. Without a supported manager, official instructions or the web remain available. Browser authorization is a separate step performed by you; installation does not log you in, request passwords or tokens, or replace public-content approval. Resume the original draft after setup. See the [account and tool guide](yida-skills/skills/yida-feedback/references/github-setup.md).
|
|
450
|
+
|
|
451
|
+
When preparing a PR that needs a personal Fork, or when you ask how to support the project, the feedback Skill can inspect account support state with `openyida feedback github status --json`. Star and a personal Fork are optional: `feedback github star --confirm` and `feedback github fork --confirm` run only after you choose them. Existing actions are reused. This check is separate from installation and automatic updates; neither action is required to submit an Issue.
|
|
450
452
|
|
|
451
453
|
<!-- OPENYIDA_COMMANDS_START -->
|
|
452
454
|
<!-- This section is generated by `npm run docs:commands`. Do not edit command rows by hand. -->
|
|
@@ -55,6 +55,7 @@ const { assertCanvasThemeStructure } = require('./canvas-theme-guard');
|
|
|
55
55
|
const { assertCanvasIconExports } = require('./canvas-icon-guard');
|
|
56
56
|
const { assertCanvasNavigationStructure } = require('./canvas-navigation-guard');
|
|
57
57
|
const { assertCanvasNavigationPaths } = require('./canvas-path-guard');
|
|
58
|
+
const { findResponsiveLayoutIssues } = require('./responsive-layout-guard');
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* 依赖 → window 别名白名单,用于把 import 改写到运行时全局对象。
|
|
@@ -977,6 +978,13 @@ function compileCanvasLocal(source, options = {}) {
|
|
|
977
978
|
assertCanvasNavigationPaths(source, options);
|
|
978
979
|
assertInlineCssStructure(source, options);
|
|
979
980
|
const warnings = [];
|
|
981
|
+
findResponsiveLayoutIssues(source).forEach((issue) => {
|
|
982
|
+
warnings.push({
|
|
983
|
+
line: issue.line,
|
|
984
|
+
rule: issue.rule,
|
|
985
|
+
message: t(issue.messageKey, ...(issue.params || [])),
|
|
986
|
+
});
|
|
987
|
+
});
|
|
980
988
|
assertCanvasThemeStructure(source, { ...options, onThemeWarning(message, details) {
|
|
981
989
|
warnings.push({ ...details, message });
|
|
982
990
|
if (options.onThemeWarning) { options.onThemeWarning(message, details); }
|
package/lib/app/page-linter.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
const { findBareCjkJsxTextIdentifiers } = require('./jsx-text-guard');
|
|
10
10
|
const { findDirectFormOpenIssues } = require('./form-open-guard');
|
|
11
11
|
const { findFormDetailLinkIssues } = require('./form-detail-link-guard');
|
|
12
|
+
const { findResponsiveLayoutIssues } = require('./responsive-layout-guard');
|
|
12
13
|
const Babel = require('@babel/standalone');
|
|
13
14
|
|
|
14
15
|
const THEN_CALLBACK_LINE_LIMIT = 50;
|
|
@@ -1051,6 +1052,15 @@ function lintYidaSource(sourceCode, filePath) {
|
|
|
1051
1052
|
findFormDetailLinkIssues(sourceCode, { parserOptions: PARSER_OPTIONS }).forEach((issue) => {
|
|
1052
1053
|
pushIssue(errors, issue.line, 'form-detail-link', t('publish.lint_form_detail_link'), disableMap);
|
|
1053
1054
|
});
|
|
1055
|
+
findResponsiveLayoutIssues(sourceCode).forEach((issue) => {
|
|
1056
|
+
pushIssue(
|
|
1057
|
+
warnings,
|
|
1058
|
+
issue.line,
|
|
1059
|
+
issue.rule,
|
|
1060
|
+
t(issue.messageKey, ...(issue.params || [])),
|
|
1061
|
+
disableMap
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1054
1064
|
return { errors, warnings };
|
|
1055
1065
|
}
|
|
1056
1066
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const FIXED_WIDTH_THRESHOLD = 480;
|
|
4
|
+
|
|
5
|
+
function getLine(source, index) {
|
|
6
|
+
return source.slice(0, index).split('\n').length;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function lineHasOptOut(source, index, marker) {
|
|
10
|
+
const lineStart = source.lastIndexOf('\n', index) + 1;
|
|
11
|
+
const lineEnd = source.indexOf('\n', index);
|
|
12
|
+
const line = source.slice(lineStart, lineEnd < 0 ? source.length : lineEnd);
|
|
13
|
+
return line.includes(marker);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find high-confidence responsive layout risks shared by Canvas and legacy JSX pages.
|
|
18
|
+
* The checks stay advisory because viewport-owned overlays and fixed-size visualizations
|
|
19
|
+
* can be intentional; authors can add the documented line marker after reviewing one.
|
|
20
|
+
* @param {string} source
|
|
21
|
+
* @returns {Array<{line: number, rule: string, messageKey: string, params?: Array<unknown>}>>}
|
|
22
|
+
*/
|
|
23
|
+
function findResponsiveLayoutIssues(source) {
|
|
24
|
+
const text = String(source || '');
|
|
25
|
+
const issues = [];
|
|
26
|
+
|
|
27
|
+
const observerMatch = /new\s+(?:window\.)?ResizeObserver\s*\(/.exec(text);
|
|
28
|
+
if (observerMatch && !/\.\s*(?:disconnect|unobserve)\s*\(/.test(text)) {
|
|
29
|
+
issues.push({
|
|
30
|
+
line: getLine(text, observerMatch.index),
|
|
31
|
+
rule: 'resize-observer-cleanup',
|
|
32
|
+
messageKey: 'publish.lint_resize_observer_cleanup',
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const viewportMatch = /(?:window\s*\.\s*innerWidth|matchMedia\s*\(\s*['"`]\s*\([^'"`]*(?:min|max)-width)/.exec(text);
|
|
37
|
+
const hasLayoutBranch = /(?:@media|grid-template-columns|gridTemplateColumns|flex-direction|flexDirection|\bmobile\b|\bisMobile\b)/.test(text);
|
|
38
|
+
const hasContainerSignal = /(?:@container|container-type|containerType|ResizeObserver|getBoundingClientRect\s*\(\s*\)\s*\.\s*width|\.\s*clientWidth\b)/.test(text);
|
|
39
|
+
if (
|
|
40
|
+
viewportMatch && hasLayoutBranch && !hasContainerSignal &&
|
|
41
|
+
!text.includes('openyida-responsive-viewport-ok')
|
|
42
|
+
) {
|
|
43
|
+
issues.push({
|
|
44
|
+
line: getLine(text, viewportMatch.index),
|
|
45
|
+
rule: 'responsive-viewport-only',
|
|
46
|
+
messageKey: 'publish.lint_responsive_viewport_only',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fixedWidthPattern = /(?:^|[\s{;,])width\s*:\s*['"]?(\d{3,})px?['"]?(?=\s*[,;}])/gm;
|
|
51
|
+
let fixedWidthMatch;
|
|
52
|
+
while ((fixedWidthMatch = fixedWidthPattern.exec(text)) !== null) {
|
|
53
|
+
const width = Number(fixedWidthMatch[1]);
|
|
54
|
+
if (
|
|
55
|
+
width >= FIXED_WIDTH_THRESHOLD &&
|
|
56
|
+
!lineHasOptOut(text, fixedWidthMatch.index, 'openyida-responsive-fixed-width-ok')
|
|
57
|
+
) {
|
|
58
|
+
issues.push({
|
|
59
|
+
line: getLine(text, fixedWidthMatch.index),
|
|
60
|
+
rule: 'responsive-fixed-width',
|
|
61
|
+
messageKey: 'publish.lint_responsive_fixed_width',
|
|
62
|
+
params: [width],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const scaleMatch = /(?:transform\s*:\s*[^;\n}]*\bscale\s*\(|\bzoom\s*:)/.exec(text);
|
|
68
|
+
if (scaleMatch && !text.includes('openyida-responsive-scale-ok')) {
|
|
69
|
+
issues.push({
|
|
70
|
+
line: getLine(text, scaleMatch.index),
|
|
71
|
+
rule: 'responsive-page-scale',
|
|
72
|
+
messageKey: 'publish.lint_responsive_page_scale',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return issues;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
FIXED_WIDTH_THRESHOLD,
|
|
81
|
+
findResponsiveLayoutIssues,
|
|
82
|
+
};
|
|
@@ -648,7 +648,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
648
648
|
mutates_yida: true,
|
|
649
649
|
mutates_local: true,
|
|
650
650
|
read_actions: ['url', 'status', 'github status'],
|
|
651
|
-
mutating_actions: ['setup', 'dismiss', 'report', 'github star', 'github fork'],
|
|
651
|
+
mutating_actions: ['setup', 'dismiss', 'report', 'github setup --install --confirm', 'github star', 'github fork'],
|
|
652
652
|
})],
|
|
653
653
|
['i18n', sideEffect('mixed', {
|
|
654
654
|
mutates_yida: true,
|
|
@@ -2058,11 +2058,13 @@ const COMMAND_GROUPS = [
|
|
|
2058
2058
|
requiresLogin: false,
|
|
2059
2059
|
}),
|
|
2060
2060
|
command('feedback', ['feedback'], 'feedback <report|github|setup|url|dismiss|status> [options]', 'help.cmd_feedback', { managedRun: { scope: 'actions', actions: { report: { scope: 'local' } } },
|
|
2061
|
-
permission: actionDependentPermission({ preauthorized_actions: ['report', 'url', 'status', 'dismiss', 'github status'], ask_actions: ['setup', 'github star', 'github fork'] }),
|
|
2061
|
+
permission: actionDependentPermission({ preauthorized_actions: ['report', 'url', 'status', 'dismiss', 'github status'], ask_actions: ['setup', 'github setup', 'github setup --install --confirm', 'github star', 'github fork'] }),
|
|
2062
2062
|
requiresLogin: false,
|
|
2063
2063
|
output: 'text|json',
|
|
2064
|
-
examples: ['openyida feedback report --input summary.json --output-dir feedback-draft-001'],
|
|
2065
|
-
|
|
2064
|
+
examples: ['openyida feedback report --input summary.json --output-dir feedback-draft-001', 'openyida feedback github setup --json', 'openyida feedback github setup --install --confirm --json'],
|
|
2065
|
+
// Keep setup conservative in permission metadata: --install changes the local machine.
|
|
2066
|
+
// The parser distinguishes read-only setup from a separately confirmed installation.
|
|
2067
|
+
notes: ['github setup [--json] detects gh and verifies the current account without installing or logging in. github setup --install --confirm performs one user-approved installation via existing Homebrew (macOS/Linux) or WinGet (Windows), then checks gh again. No manager bootstrap, source changes, automatic retry, upgrade, or automatic login; unsupported environments use official instructions or the web. Browser authorization requires separate consent and public submission still requires content review. report prepares local review drafts only; no login, source checkout, update check or network submission. github status [--json] checks the authenticated github.com user, Star and personal Fork through an existing gh installation; it performs no writes. github star|fork --confirm [--json] requires the user to explicitly choose that account action. Missing gh/login, unknown status and declined actions never block Issue submission. Already applied actions are reused; writes are single-attempt with readback. No auto-update/install hook, Issue submission, clone or git remote modification. Managed runs permit only offline report preparation.'],
|
|
2066
2068
|
}),
|
|
2067
2069
|
command('batch', ['batch'], 'batch <file>|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]', 'help.cmd_batch', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
|
|
2068
2070
|
output: 'text|json',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -1534,6 +1534,10 @@ Examples:
|
|
|
1534
1534
|
lint_setstate_non_timestamp: 'this.setState writes a non-timestamp field. Custom page business state should live in _customState and be updated via forceUpdate()/setCustomState(); this.setState should only carry the timestamp contract field',
|
|
1535
1535
|
lint_self_binding_missing: 'renderJsx uses this without declaring var self = this;. Add var self = this; at the top of renderJsx and use self inside closures/callbacks to avoid losing this',
|
|
1536
1536
|
lint_echarts_dom_ready: 'echarts.init is not wrapped in setTimeout; the container may not be mounted yet so the chart may not render. Use setTimeout(function(){ /* echarts.init */ }, 300)',
|
|
1537
|
+
lint_resize_observer_cleanup: 'ResizeObserver has no disconnect()/unobserve() cleanup and may keep measuring after unmount. Release it from the effect or lifecycle cleanup.',
|
|
1538
|
+
lint_responsive_viewport_only: 'Layout changes only from viewport width, so a narrow desktop sidebar, drawer, or iframe may not degrade. Measure the content container with @container or a cleaned-up ResizeObserver. Add openyida-responsive-viewport-ok only for intentional viewport-owned behavior.',
|
|
1539
|
+
lint_responsive_fixed_width: 'A fixed width of %spx may overflow a narrow container. Prefer max-width, minmax(0, 1fr), or local scrolling. Add openyida-responsive-fixed-width-ok only for an intentional fixed canvas.',
|
|
1540
|
+
lint_responsive_page_scale: 'Whole-page scale/zoom was detected. Responsive layouts should reflow content while preserving readable text and touch targets. Add openyida-responsive-scale-ok only for an intentionally scaled local visualization.',
|
|
1537
1541
|
lint_yida_api_catch: 'this.utils.yida API call has no detected .catch(); add error handling and toast the user',
|
|
1538
1542
|
lint_echarts_legacy_map_china: 'ECharts 5 no longer supports echarts/map/js/china.js. Load DataV GeoJSON and call echarts.registerMap("china", geoJson) instead',
|
|
1539
1543
|
lint_echarts_rich_label_formatter: 'ECharts label.formatter returning rich text templates is unstable in Yida custom pages; prefer plain formatter strings or precomputed label text',
|
|
@@ -2440,6 +2444,26 @@ Object.assign(module.exports.help, {'cmd_formula_contracts': 'Show formula forma
|
|
|
2440
2444
|
Object.assign(module.exports.formula_v2, {'parallel_formula': 'Use conditions/rules or all:true for parallel branches; expression is currently supported only for route branches.'});
|
|
2441
2445
|
|
|
2442
2446
|
module.exports.feedback_report = {
|
|
2447
|
+
body: {
|
|
2448
|
+
summary: 'Your goal',
|
|
2449
|
+
actual: 'What happened',
|
|
2450
|
+
expected: 'Expected result',
|
|
2451
|
+
incident: 'Environment when the problem occurred',
|
|
2452
|
+
command: 'Related operation',
|
|
2453
|
+
steps: 'How to reproduce',
|
|
2454
|
+
impact: 'Impact',
|
|
2455
|
+
evidence: 'Observed evidence',
|
|
2456
|
+
attempts: 'Attempts and results',
|
|
2457
|
+
cause: 'Cause and confidence',
|
|
2458
|
+
cause_evidence: 'Evidence for assessment',
|
|
2459
|
+
next: 'Next verification',
|
|
2460
|
+
screenshots: 'Screenshots',
|
|
2461
|
+
review_image: 'Review image before sharing',
|
|
2462
|
+
limitations: 'Test limitations',
|
|
2463
|
+
generator: 'Report generator environment (not the incident environment)',
|
|
2464
|
+
unknown: 'Unknown / not verified',
|
|
2465
|
+
draft: 'Local draft — not submitted. Review text and screenshots for customer data, credentials and private information before sharing.',
|
|
2466
|
+
},
|
|
2443
2467
|
comparison: 'Test pass/fail requires a command, actual result and evidence.',
|
|
2444
2468
|
screenshots: 'Choose at most five PNG/JPEG/WebP screenshots of at most 10 MiB each.',
|
|
2445
2469
|
text: 'Feedback text fields must be strings of at most 12000 characters.',
|
|
@@ -2527,7 +2551,16 @@ module.exports.task_center = {
|
|
|
2527
2551
|
};
|
|
2528
2552
|
|
|
2529
2553
|
module.exports.feedback_github = {
|
|
2530
|
-
|
|
2554
|
+
setup_inspect_installation: 'Installation is unverified. Check gh --version, package-manager state or reopen the terminal before deciding to retry; keep the draft.',
|
|
2555
|
+
setup_check_execution: 'gh could not run. Check execution permissions or the local environment; do not reinstall or switch accounts automatically.',
|
|
2556
|
+
setup_confirm_install: 'GitHub CLI can be installed. Explain the plan and obtain consent before openyida feedback github setup --install --confirm --json; the web option remains available.',
|
|
2557
|
+
setup_manual_install: 'Automatic installation is unavailable. Use the official instructions, an existing connector or the web; keep the draft.',
|
|
2558
|
+
setup_ready: 'GitHub CLI and the current account are verified. Resume the draft; confirm public content and required permissions before submission.',
|
|
2559
|
+
setup_browser_login: 'GitHub CLI is available and login is required. With consent, guide browser authorization, then rerun setup --json to verify.',
|
|
2560
|
+
setup_check_account: 'GitHub CLI is available, but the account is unverified. Check network, authorization or rate limits; do not infer a missing account or reinstall.',
|
|
2561
|
+
setup_consent: 'Use --install --confirm only after consent to install GitHub CLI and required dependencies in the package manager default location. Windows accepts WinGet source/package agreements and may need user-approved system permissions. No automatic manager installation, privilege escalation or login.',
|
|
2562
|
+
setup_privacy: 'Installation is not consent to publish. The user authorizes in the browser; never request passwords, codes or tokens. If gh cannot store credentials securely, use the web or a secure connector. Keep account and tool diagnostics out of feedback reports.',
|
|
2563
|
+
usage: 'Usage: openyida feedback github setup [--install --confirm] [--json]; openyida feedback github <status|star|fork> [--confirm] [--json]',
|
|
2531
2564
|
confirmation: 'Star or Fork requires the user to explicitly choose that action before --confirm. Neither is required to submit an Issue.',
|
|
2532
2565
|
failed: 'The GitHub action could not be verified. Inspect its status before retrying; this does not block Issue submission.',
|
|
2533
2566
|
};
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -1531,6 +1531,10 @@ openyida - 宜搭命令行工具
|
|
|
1531
1531
|
lint_setstate_non_timestamp: '检测到 this.setState 写入非 timestamp 字段。业务态应写入 _customState 并调用 forceUpdate()/setCustomState() 触发重渲染;this.setState 仅用于 timestamp 契约字段',
|
|
1532
1532
|
lint_self_binding_missing: 'renderJsx 内使用了 this 但未声明 var self = this;。请在 renderJsx 开头加入 var self = this;,闭包/回调内改用 self,避免 this 指向丢失',
|
|
1533
1533
|
lint_echarts_dom_ready: 'echarts.init 未包裹在 setTimeout 中,容器可能尚未挂载导致图表不渲染。建议 setTimeout(function(){ /* echarts.init */ }, 300)',
|
|
1534
|
+
lint_resize_observer_cleanup: 'ResizeObserver 缺少 disconnect()/unobserve() 清理,页面卸载后可能继续测量。请在 effect 或生命周期清理函数中释放 observer。',
|
|
1535
|
+
lint_responsive_viewport_only: '布局只依据视口宽度变化,桌面侧栏、抽屉或 iframe 窄容器可能无法降级。请用 @container 或带清理的 ResizeObserver 测量实际内容容器;若确实是视口级行为,添加 openyida-responsive-viewport-ok 说明。',
|
|
1536
|
+
lint_responsive_fixed_width: '检测到固定宽度 %spx,窄容器可能产生整页横向溢出。请改用 max-width、minmax(0, 1fr) 或局部滚动;确有固定画布需求时添加 openyida-responsive-fixed-width-ok 说明。',
|
|
1537
|
+
lint_responsive_page_scale: '检测到 scale/zoom 整体缩放。响应式布局应重排内容并保留可读字号和点击区域;仅局部可视化确需缩放时添加 openyida-responsive-scale-ok 说明。',
|
|
1534
1538
|
lint_yida_api_catch: 'this.utils.yida API 调用未检测到 .catch(),请补充错误处理并 toast 给用户',
|
|
1535
1539
|
lint_echarts_legacy_map_china: 'ECharts 5 已废弃 echarts/map/js/china.js,请加载 DataV GeoJSON 后调用 echarts.registerMap("china", geoJson)',
|
|
1536
1540
|
lint_echarts_rich_label_formatter: 'ECharts label.formatter 返回 rich text 模板在宜搭自定义页面环境不稳定,建议使用普通 formatter 字符串或预先拼好的标签文本',
|
|
@@ -2394,6 +2398,26 @@ Object.assign(module.exports.help, {'cmd_formula_contracts': '查看公式保存
|
|
|
2394
2398
|
Object.assign(module.exports.formula_v2, {'parallel_formula': '并行分支使用 conditions/rules 或 all:true;expression 目前只适用于 route 分支。'});
|
|
2395
2399
|
|
|
2396
2400
|
module.exports.feedback_report = {
|
|
2401
|
+
body: {
|
|
2402
|
+
summary: '想完成什么',
|
|
2403
|
+
actual: '实际发生了什么',
|
|
2404
|
+
expected: '希望看到什么结果',
|
|
2405
|
+
incident: '问题发生时的环境',
|
|
2406
|
+
command: '相关操作',
|
|
2407
|
+
steps: '怎样重现',
|
|
2408
|
+
impact: '影响',
|
|
2409
|
+
evidence: '已观察的证据',
|
|
2410
|
+
attempts: '尝试过的方法与结果',
|
|
2411
|
+
cause: '可能的原因及确认程度',
|
|
2412
|
+
cause_evidence: '判断依据',
|
|
2413
|
+
next: '下一步验证',
|
|
2414
|
+
screenshots: '问题截图',
|
|
2415
|
+
review_image: '公开前请检查图片',
|
|
2416
|
+
limitations: '尚未验证的范围',
|
|
2417
|
+
generator: '报告生成环境(不代表问题发生时)',
|
|
2418
|
+
unknown: '未知 / 尚未确认',
|
|
2419
|
+
draft: '本地草稿,尚未提交。请检查文字和截图,确认没有客户数据、凭证或内部信息后再公开。',
|
|
2420
|
+
},
|
|
2397
2421
|
comparison: '测试通过或失败须提供真实命令、实际结果及证据。',
|
|
2398
2422
|
screenshots: '截图须为明确选择的 PNG/JPEG/WebP,每张至多 10 MiB,最多 5 张。',
|
|
2399
2423
|
text: '反馈文本字段须为不超过 12000 字符的字符串。',
|
|
@@ -2481,7 +2505,16 @@ module.exports.task_center = {
|
|
|
2481
2505
|
};
|
|
2482
2506
|
|
|
2483
2507
|
module.exports.feedback_github = {
|
|
2484
|
-
|
|
2508
|
+
setup_inspect_installation: '安装结果尚未核实。先检查 gh --version、包管理器状态或重新打开终端,再决定是否重试;草稿仍保留。',
|
|
2509
|
+
setup_check_execution: 'gh 暂时无法运行。请检查执行权限或本机环境;不要重复安装或更换账号。',
|
|
2510
|
+
setup_confirm_install: '可安装 GitHub CLI。先说明安装计划并取得用户同意,再执行 openyida feedback github setup --install --confirm --json;也可使用网页。',
|
|
2511
|
+
setup_manual_install: '当前没有可用的自动安装方式。可查看官方安装说明、使用已有连接器或网页,草稿仍保留。',
|
|
2512
|
+
setup_ready: 'GitHub CLI 和当前账号已验证。继续原草稿,提交前仍需核对公开内容和所需权限。',
|
|
2513
|
+
setup_browser_login: 'GitHub CLI 已可用,需要登录。用户同意后引导浏览器授权,完成后再次运行 setup --json 核实。',
|
|
2514
|
+
setup_check_account: 'GitHub CLI 已可用,但当前账号尚未核实。先检查网络、授权或限流;不要据此判断用户没有账号或要求重新安装。',
|
|
2515
|
+
setup_consent: '仅在用户同意安装 GitHub CLI 及其必要依赖后使用 --install --confirm。使用包管理器默认位置;Windows 安装会接受 WinGet 来源和软件协议,可能需要用户批准系统权限。不自动安装包管理器、提权或登录。',
|
|
2516
|
+
setup_privacy: '安装不代表同意公开提交。浏览器授权由用户完成;不索取密码、验证码或 Token。若 gh 无法安全保存凭证,改用网页或安全连接器;账号和工具诊断不写入反馈报告。',
|
|
2517
|
+
usage: '用法:openyida feedback github setup [--install --confirm] [--json]; openyida feedback github <status|star|fork> [--confirm] [--json]',
|
|
2485
2518
|
confirmation: 'Star 或 Fork 需要用户明确选择后再传入 --confirm;提交 Issue 不要求这两项操作。',
|
|
2486
2519
|
failed: 'GitHub 操作未能核实完成,请先查询状态;这不影响提交 Issue。',
|
|
2487
2520
|
};
|
package/lib/core/sample.js
CHANGED
|
@@ -60,6 +60,7 @@ const SAMPLES = {
|
|
|
60
60
|
'canvas-theme': 'canvas-theme-provider.template.jsx',
|
|
61
61
|
'canvas-navigation': 'canvas-navigation.jsx',
|
|
62
62
|
'canvas-view-state': 'canvas-view-state.jsx',
|
|
63
|
+
'canvas-responsive-layout': 'canvas-responsive-layout.jsx',
|
|
63
64
|
'canvas-admin-entry': 'canvas-admin-entry.jsx',
|
|
64
65
|
'canvas-nav-side': 'canvas-nav/side.jsx',
|
|
65
66
|
'canvas-nav-top': 'canvas-nav/top.jsx',
|
|
@@ -109,6 +110,7 @@ const SAMPLE_OUTPUT_FILENAMES = {
|
|
|
109
110
|
'openyida-page-template/form-open-container': 'form-open-container.jsx',
|
|
110
111
|
'openyida-page-template/canvas-dialog': 'canvas-dialog.jsx',
|
|
111
112
|
'openyida-page-template/canvas-theme': 'canvas-theme.jsx',
|
|
113
|
+
'openyida-page-template/canvas-responsive-layout': 'canvas-responsive-layout.jsx',
|
|
112
114
|
'yida-design/app-theme': 'app-theme.css',
|
|
113
115
|
};
|
|
114
116
|
|
|
@@ -116,6 +118,7 @@ const SAMPLE_HINTS = {
|
|
|
116
118
|
'openyida-page-template/canvas-admin-entry': '已包含统一跳转 helper;在 CanvasThemeProvider 内渲染 CanvasAdminWorkbenchButton,填写当前 appType 与真实 workbenchFormUuid。身份读取当前应用 loginUser.isAppAdmin,未知时隐藏;默认不跳开发后台或猜测管理首页。',
|
|
117
119
|
'openyida-page-template/canvas-navigation': '先从资源回读填写 appType、目标类型与页面 ID;本页 Tab 用 selectView,独立前台用 custom,管理入口用 page,表单入口接 openForm;按钮和链接共用 buildCanvasPageUrl。',
|
|
118
120
|
'openyida-page-template/canvas-theme': '以 CanvasThemeProvider 包住业务组件;Tabs 用于内容切换,Segmented/Radio 用于筛选,Button 用于动作。默认轻底选中态与可读文字,controls 可供自绘控件复用;避免内层固定色覆盖。保留内置加载遮罩防闪边样式;旧页面需更新 Provider 并重新发布。',
|
|
121
|
+
'openyida-page-template/canvas-responsive-layout': '合并 useContainerLayout 与 ResponsiveLayout;按实际内容容器重排筛选栏和主辅面板,宽表仅局部滚动,图表按 data-chart-width resize。业务状态放在布局壳之上,断点切换不得修改 key 或重挂载。',
|
|
119
122
|
'openyida-page-template/canvas-nav-top': '顶部默认贴顶通栏;长页先合并 canvas-nav-content,CanvasNav 传 headerOnly,有首屏背景时传 overlay;悬浮外观仅显式 floating=true。',
|
|
120
123
|
'openyida-page-template/canvas-nav-content': '合并 CanvasNavigationContent;长页用 layout=document,CanvasNav 传 headerOnly/overlay;默认 sticky,受宿主限制时用 navigationPosition=fixed。工作区用 workspace,宿主已分配高度时传 height。',
|
|
121
124
|
'openyida-page-template/canvas-nav-data': '页内展示用 mode=local(key/viewKey/targetType=local),不请求平台菜单;platform 需真实资源 ID;independent 或声明 access 时必须接入真实 resolveAccess。保留 PRD 顺序与分组。',
|
package/lib/feedback/feedback.js
CHANGED
|
@@ -51,6 +51,7 @@ function printUsage() {
|
|
|
51
51
|
openyida feedback setup <appType> [--name "OpenYida 体验反馈"] [--path /o/openyida-feedback] [--open|--no-open]
|
|
52
52
|
openyida feedback setup --create-app [--app-name "OpenYida 体验反馈"] [--name "OpenYida 体验反馈"]
|
|
53
53
|
openyida feedback report --input <summary.json> --output-dir <new-directory>
|
|
54
|
+
openyida feedback github setup [--install --confirm] [--json]
|
|
54
55
|
openyida feedback github <status|star|fork> [--confirm] [--json]
|
|
55
56
|
openyida feedback url [--tool Codex] [--model gpt-5] [--command publish] [--diagnostics "..."]
|
|
56
57
|
openyida feedback dismiss --today|--forever|--reset
|
|
@@ -58,7 +59,7 @@ function printUsage() {
|
|
|
58
59
|
|
|
59
60
|
说明:
|
|
60
61
|
${t('feedback_report.help')}
|
|
61
|
-
setup 会在宜搭创建一个公开反馈表单,并写入本地 .cache/openyida/feedback/config.json。
|
|
62
|
+
feedback setup 会在宜搭创建一个公开反馈表单,并写入本地 .cache/openyida/feedback/config.json。
|
|
62
63
|
url 只生成带脱敏元数据的填写链接,不提交任何网络请求。
|
|
63
64
|
dismiss/status 只读写本地提醒状态。
|
|
64
65
|
`);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const { t } = require('../core/i18n');
|
|
5
|
+
|
|
6
|
+
const INSTALL_URL = 'https://github.com/cli/cli#installation';
|
|
7
|
+
const LOGIN_COMMAND = 'gh auth login --hostname github.com --web --skip-ssh-key';
|
|
8
|
+
|
|
9
|
+
/** Fixed argv only. Capture diagnostics privately, including package-manager failures. */
|
|
10
|
+
function invoke(binary, args, options, installing = false) {
|
|
11
|
+
try {
|
|
12
|
+
return (options.spawnSyncFn || spawnSync)(binary, args, {
|
|
13
|
+
shell: false, encoding: 'utf8', timeout: installing ? 300000 : 15000,
|
|
14
|
+
maxBuffer: 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
|
15
|
+
env: { ...(options.env || process.env), GH_HOST: 'github.com', GH_PROMPT_DISABLED: '1',
|
|
16
|
+
GIT_TERMINAL_PROMPT: '0', HOMEBREW_NO_AUTO_UPDATE: '1', HOMEBREW_NO_INSTALL_CLEANUP: '1',
|
|
17
|
+
HOMEBREW_NO_INSTALL_UPGRADE: '1', HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: '1', HOMEBREW_NO_SUDO: '1',
|
|
18
|
+
...(installing ? { HOMEBREW_NO_ASK: '1' } : {}) },
|
|
19
|
+
});
|
|
20
|
+
} catch { return { status: null }; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toolState(options) {
|
|
24
|
+
const result = invoke('gh', ['--version'], options);
|
|
25
|
+
if (result.error?.code === 'ENOENT') { return { state: 'missing', version: null }; }
|
|
26
|
+
const version = String(result.stdout || '').match(/^gh version (\d+\.\d+\.\d+)\b/)?.[1];
|
|
27
|
+
return result.status === 0 && !result.error && version
|
|
28
|
+
? { state: 'available', version } : { state: 'unverified', version: null };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function installPlan(options) {
|
|
32
|
+
const platform = options.platform || process.platform;
|
|
33
|
+
const binary = platform === 'win32' ? 'winget' : ['darwin', 'linux'].includes(platform) ? 'brew' : null;
|
|
34
|
+
if (!binary) { return { available: false, reason: 'unsupported_platform', url: INSTALL_URL }; }
|
|
35
|
+
const probe = invoke(binary, ['--version'], options);
|
|
36
|
+
if (probe.error || probe.status !== 0) {
|
|
37
|
+
return { available: false, reason: probe.error?.code === 'ENOENT' ? 'manager_missing' : 'manager_unverified', url: INSTALL_URL };
|
|
38
|
+
}
|
|
39
|
+
const args = binary === 'brew' ? ['install', 'homebrew/core/gh']
|
|
40
|
+
: ['install', '--id', 'GitHub.cli', '--exact', '--source', 'winget', '--no-upgrade', '--disable-interactivity',
|
|
41
|
+
'--accept-source-agreements', '--accept-package-agreements'];
|
|
42
|
+
return { available: true, binary, args, url: INSTALL_URL, requiresConsent: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function accountState(options) {
|
|
46
|
+
const result = invoke('gh', ['api', '--hostname', 'github.com', '--method', 'GET', 'user'], options);
|
|
47
|
+
if (result.status === 4 && !result.error) { return { state: 'login_required', login: null }; }
|
|
48
|
+
let account;
|
|
49
|
+
try { account = JSON.parse(result.stdout); } catch { /* Unknown response stays unverified. */ }
|
|
50
|
+
if (result.status === 0 && !result.error && account?.type === 'User'
|
|
51
|
+
&& /^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(account.login || '')) {
|
|
52
|
+
return { state: 'verified', login: account.login };
|
|
53
|
+
}
|
|
54
|
+
return { state: 'unverified', login: null };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Called only after the router validates consent and the managed-run boundary. */
|
|
58
|
+
function setup(parsed, options) {
|
|
59
|
+
let tool = toolState(options);
|
|
60
|
+
const installation = { attempted: false, state: 'not_requested', plan: null };
|
|
61
|
+
if (tool.state === 'missing') {
|
|
62
|
+
installation.plan = installPlan(options);
|
|
63
|
+
if (parsed.install) {
|
|
64
|
+
installation.state = 'unavailable';
|
|
65
|
+
if (installation.plan.available) {
|
|
66
|
+
installation.attempted = true;
|
|
67
|
+
const result = invoke(installation.plan.binary, installation.plan.args, options, true);
|
|
68
|
+
tool = toolState(options);
|
|
69
|
+
installation.state = result.status === 0 && !result.error && tool.state === 'available'
|
|
70
|
+
? 'verified' : 'unverified';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else if (parsed.install) {
|
|
74
|
+
installation.state = tool.state === 'available' ? 'already_available' : 'unavailable';
|
|
75
|
+
}
|
|
76
|
+
const account = tool.state === 'available' ? accountState(options) : { state: 'not_checked', login: null };
|
|
77
|
+
const ready = tool.state === 'available' && account.state === 'verified';
|
|
78
|
+
const nextStep = installation.state === 'unverified' ? 'inspect_installation'
|
|
79
|
+
: tool.state === 'unverified' ? 'check_execution'
|
|
80
|
+
: tool.state === 'missing' ? (installation.plan?.available ? 'confirm_install' : 'manual_install')
|
|
81
|
+
: ready ? 'ready' : account.state === 'login_required' ? 'browser_login' : 'check_account';
|
|
82
|
+
return {
|
|
83
|
+
success: !parsed.install || ['verified', 'already_available'].includes(installation.state),
|
|
84
|
+
action: 'setup', tool, account, ready, installation, nextStep,
|
|
85
|
+
message: t(`feedback_github.setup_${nextStep}`),
|
|
86
|
+
consentNotice: t('feedback_github.setup_consent'),
|
|
87
|
+
privacyNotice: t('feedback_github.setup_privacy'),
|
|
88
|
+
authorization: { performed: false, requiresSeparateConsent: true,
|
|
89
|
+
command: tool.state === 'available' && !ready ? LOGIN_COMMAND : null,
|
|
90
|
+
url: 'https://cli.github.com/manual/gh_auth_login' },
|
|
91
|
+
links: { install: INSTALL_URL, signup: 'https://github.com/signup', issue: 'https://github.com/openyida/openyida/issues/new' },
|
|
92
|
+
submissionPerformed: false, publicConfirmationRequired: true,
|
|
93
|
+
inspectBeforeRetry: installation.state === 'unverified',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { setup };
|
package/lib/feedback/github.js
CHANGED
|
@@ -11,15 +11,18 @@ const links = Object.freeze({ repository: `https://github.com/${REPOSITORY}`,
|
|
|
11
11
|
/** Parse explicit account actions; submitting an Issue never implies Star or Fork consent. */
|
|
12
12
|
function parseArgs(args = []) {
|
|
13
13
|
const [action, ...flags] = args;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|| (
|
|
14
|
+
const install = flags.includes('--install');
|
|
15
|
+
if (!['status', 'star', 'fork', 'setup'].includes(action) || new Set(flags).size !== flags.length
|
|
16
|
+
|| flags.some(flag => !['--json', '--confirm', '--install'].includes(flag))
|
|
17
|
+
|| (install && action !== 'setup')
|
|
18
|
+
|| (action === 'status' && flags.includes('--confirm'))
|
|
19
|
+
|| (action === 'setup' && !install && flags.includes('--confirm'))) {
|
|
17
20
|
throw new CliError(t('feedback_github.usage'), { code: 'FEEDBACK_GITHUB_USAGE' });
|
|
18
21
|
}
|
|
19
|
-
if (
|
|
20
|
-
throw new CliError(t('feedback_github.confirmation'), { code: 'FEEDBACK_GITHUB_CONFIRMATION_REQUIRED' });
|
|
22
|
+
if ((['star', 'fork'].includes(action) || install) && !flags.includes('--confirm')) {
|
|
23
|
+
throw new CliError(t(install ? 'feedback_github.setup_consent' : 'feedback_github.confirmation'), { code: 'FEEDBACK_GITHUB_CONFIRMATION_REQUIRED' });
|
|
21
24
|
}
|
|
22
|
-
return { action, confirm: flags.includes('--confirm') };
|
|
25
|
+
return { action, confirm: flags.includes('--confirm'), ...(action === 'setup' ? { install } : {}) };
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
/** Run gh against github.com only. Never expose its raw output or credential diagnostics. */
|
|
@@ -90,10 +93,12 @@ function inspect(options) {
|
|
|
90
93
|
|
|
91
94
|
/** Execute one explicitly authorized action, then read it back. No retry, clone, remote change or Issue submission. */
|
|
92
95
|
function execute(parsed, options = {}) {
|
|
93
|
-
const
|
|
96
|
+
const validated = parseArgs([parsed.action, ...(parsed.confirm === true ? ['--confirm'] : []), ...(parsed.install === true ? ['--install'] : [])]);
|
|
97
|
+
const { action } = validated;
|
|
94
98
|
if ((options.env || process.env).OPENYIDA_MANAGED_RUN === '1') {
|
|
95
99
|
throw new CliError(t('feedback_github.failed'), { code: 'FEEDBACK_GITHUB_MANAGED_DENIED' });
|
|
96
100
|
}
|
|
101
|
+
if (action === 'setup') { return require('./github-setup').setup(validated, options); }
|
|
97
102
|
const result = inspect(options);
|
|
98
103
|
if (action === 'status') { return result; }
|
|
99
104
|
const fail = (stage, mutationPerformed = false) => {
|
|
@@ -124,7 +129,11 @@ function execute(parsed, options = {}) {
|
|
|
124
129
|
/** CLI entrypoint. The confirmation flag attests to the user's explicit choice of this action. */
|
|
125
130
|
function run(args = []) {
|
|
126
131
|
const parsed = parseArgs(args);
|
|
127
|
-
|
|
132
|
+
const result = execute(parsed);
|
|
133
|
+
if (result.success === false) {
|
|
134
|
+
throw new CliError(result.message, { code: 'FEEDBACK_GITHUB_SETUP_FAILED', details: result });
|
|
135
|
+
}
|
|
136
|
+
console.log(JSON.stringify(result, null, 2));
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
module.exports = { parseArgs, execute, run };
|
package/lib/feedback/report.js
CHANGED
|
@@ -13,6 +13,15 @@ const MAX_INPUT_BYTES = 1024 * 1024;
|
|
|
13
13
|
const REPOSITORY = 'openyida/openyida';
|
|
14
14
|
const TEXT_FIELDS = ['title', 'summary', 'command', 'expected', 'actual', 'impact', 'tool', 'model'];
|
|
15
15
|
const LIST_FIELDS = ['steps', 'evidence', 'attempts'];
|
|
16
|
+
const INCIDENT_FIELDS = ['openyida', 'node', 'platform', 'arch', 'installation', 'browser', 'deployment', 'organizationContext'];
|
|
17
|
+
|
|
18
|
+
function incidentEnvironment(value) {
|
|
19
|
+
if (value === undefined) {value = {};}
|
|
20
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {throw invalid('object');}
|
|
21
|
+
// Only explicit incident facts belong here. The reporting process may run
|
|
22
|
+
// after an upgrade or on another machine; never infer incident versions.
|
|
23
|
+
return Object.fromEntries(INCIDENT_FIELDS.map(key => [key, sanitizeText(value[key])]));
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
function comparisonRun(value) {
|
|
18
27
|
if (value === undefined) {return { status: 'not_run', evidence: [] };}
|
|
@@ -98,6 +107,8 @@ function buildReport(input) {
|
|
|
98
107
|
throw invalid('confirmed');
|
|
99
108
|
}
|
|
100
109
|
report.screenshots = [];
|
|
110
|
+
report.incidentEnvironment = incidentEnvironment(input.incidentEnvironment);
|
|
111
|
+
// Retain this output contract for existing consumers: generator context only.
|
|
101
112
|
report.environment = {
|
|
102
113
|
openyida: require('../../package.json').version,
|
|
103
114
|
node: process.version,
|
|
@@ -114,17 +125,28 @@ function buildReport(input) {
|
|
|
114
125
|
|
|
115
126
|
// Escape markup so copied diagnostics cannot inject images, links or HTML in an issue.
|
|
116
127
|
function markdownText(value) {
|
|
117
|
-
return String(value || '
|
|
128
|
+
return String(value || t('feedback_report.body.unknown')).replace(/[\\`*_{}[\]<>()#!|~]/g, '\\$&');
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
function renderMarkdown(report) {
|
|
132
|
+
const label = key => t('feedback_report.body.' + key);
|
|
133
|
+
const incident = report.incidentEnvironment || {};
|
|
134
|
+
const environmentLines = Object.entries({
|
|
135
|
+
openyida: incident.openyida, node: incident.node, platform: incident.platform, arch: incident.arch,
|
|
136
|
+
installation: incident.installation, tool: report.tool, model: report.model,
|
|
137
|
+
...Object.fromEntries(['browser', 'deployment', 'organizationContext']
|
|
138
|
+
.filter(key => incident[key]).map(key => [key, incident[key]])),
|
|
139
|
+
}).map(([key, value]) => `${key}: ${value || label('unknown')}`);
|
|
121
140
|
const sections = [
|
|
122
|
-
['
|
|
123
|
-
['
|
|
124
|
-
['
|
|
125
|
-
['
|
|
126
|
-
['
|
|
127
|
-
|
|
141
|
+
[label('summary'), report.summary || label('unknown')],
|
|
142
|
+
[label('actual'), report.actual], [label('expected'), report.expected || label('unknown')],
|
|
143
|
+
[label('incident'), environmentLines],
|
|
144
|
+
[label('command'), report.command], [label('steps'), report.steps],
|
|
145
|
+
[label('impact'), report.impact], [label('evidence'), report.evidence], [label('attempts'), report.attempts],
|
|
146
|
+
...(report.cause.summary || report.cause.status !== 'unknown' || report.cause.category !== 'unknown'
|
|
147
|
+
? [[label('cause'), `${report.cause.category} / ${report.cause.status}\n${report.cause.summary}`]] : []),
|
|
148
|
+
[label('cause_evidence'), report.cause.evidence], [label('next'), report.cause.nextSteps],
|
|
149
|
+
[label('screenshots'), report.screenshots.map(item => `${item.file}: ${item.description || label('review_image')}`)],
|
|
128
150
|
...(['before', 'after', 'regression'].some(key => report.comparison[key].status !== 'not_run') || report.comparison.limitations.length ? ['before', 'after', 'regression'] : []).map(key => [
|
|
129
151
|
`Test comparison: ${key}`, [
|
|
130
152
|
`Status: ${report.comparison[key].status}`,
|
|
@@ -134,19 +156,17 @@ function renderMarkdown(report) {
|
|
|
134
156
|
...(report.comparison[key].evidence || []),
|
|
135
157
|
],
|
|
136
158
|
]),
|
|
137
|
-
...(report.comparison.limitations.length ? [['
|
|
138
|
-
|
|
159
|
+
...(report.comparison.limitations.length ? [[label('limitations'), report.comparison.limitations]] : []),
|
|
160
|
+
[label('generator'), Object.entries(report.environment).map(([key, value]) => `${key}: ${value}`)],
|
|
161
|
+
].filter(([, value]) => Array.isArray(value) ? value.length : Boolean(value));
|
|
139
162
|
return [
|
|
140
163
|
`# ${markdownText(report.title)}`, '',
|
|
141
|
-
'
|
|
142
|
-
`Fingerprint: ${report.fingerprint}`, `Observed occurrences: ${report.occurrenceCount || 'Unknown'}`, '',
|
|
143
|
-
'## Environment', '',
|
|
144
|
-
...Object.entries({ ...report.environment, tool: report.tool, model: report.model })
|
|
145
|
-
.map(([key, value]) => `- ${key}: ${markdownText(value)}`), '',
|
|
164
|
+
label('draft'), '',
|
|
146
165
|
...sections.flatMap(([heading, value]) => [
|
|
147
166
|
`## ${heading}`, '',
|
|
148
|
-
Array.isArray(value) ?
|
|
167
|
+
Array.isArray(value) ? value.map(item => `- ${markdownText(item)}`).join('\n') : markdownText(value), '',
|
|
149
168
|
]),
|
|
169
|
+
`Fingerprint: ${report.fingerprint}`, `Observed occurrences: ${report.occurrenceCount || label('unknown')}`, '',
|
|
150
170
|
].join('\n');
|
|
151
171
|
}
|
|
152
172
|
|
|
@@ -12,7 +12,7 @@ function CanvasNav({ items = [], activeKey, onSelect, title, logo, actions, chil
|
|
|
12
12
|
.oy-canvas-nav.oy-nav-mixed .oy-nav-sidebar { padding: var(--pod-nav-l-aside-padding, 8px); }
|
|
13
13
|
.oy-nav-mixed .oy-nav-content { flex: 1; }
|
|
14
14
|
.oy-nav-mixed .oy-nav-menu { display: flex; flex-direction: column; gap: var(--pod-nav-menu-gap, 8px); }
|
|
15
|
-
@
|
|
15
|
+
@container (max-width: 767px) {
|
|
16
16
|
.oy-nav-mixed .oy-nav-groups { flex-basis: 100%; }
|
|
17
17
|
.oy-nav-mixed .oy-nav-body { display: block; }
|
|
18
18
|
.oy-nav-mixed .oy-nav-menu { flex-direction: row; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--pod-nav-sub-divider-color, var(--color-line1-1, #ddd)); }
|
|
@@ -4,7 +4,7 @@ import React from 'react';
|
|
|
4
4
|
// 这是可选实现示例;导航外观按 design.md 设计,已有 UI 无需替换成示例默认样式。
|
|
5
5
|
// 选中菜单标识当前页面,main 用 aria-label 命名,children 直接承载业务内容。
|
|
6
6
|
const CANVAS_NAV_CSS = `
|
|
7
|
-
.oy-canvas-nav { display: flow-root; min-width: 0; min-height: 100vh; background: var(--pod-page-bg-color, var(--color-white, #fff)); }
|
|
7
|
+
.oy-canvas-nav { container-type: inline-size; display: flow-root; min-width: 0; min-height: 100vh; background: var(--pod-page-bg-color, var(--color-white, #fff)); }
|
|
8
8
|
.oy-canvas-nav .oy-nav-surface {
|
|
9
9
|
background: var(--pod-shell-theme-bg-color, var(--pod-nav-popup-bg-color, var(--color-white, #fff)));
|
|
10
10
|
color: var(--pod-nav-item-text-color, var(--color-text1-4, #1f2329));
|
|
@@ -6,7 +6,7 @@ function CanvasNav({ items = [], activeKey, onSelect, title, logo, actions, chil
|
|
|
6
6
|
.oy-nav-side .oy-nav-content { flex: 1; }
|
|
7
7
|
.oy-nav-side .oy-nav-brand { min-height: var(--pod-nav-platform-header-height, 48px); }
|
|
8
8
|
.oy-nav-side .oy-nav-menu { display: flex; flex-direction: column; gap: var(--pod-nav-menu-gap, 8px); }
|
|
9
|
-
@
|
|
9
|
+
@container (max-width: 767px) { .oy-nav-side { display: block; } }
|
|
10
10
|
`}</style>
|
|
11
11
|
<CanvasSidebar title={title} logo={logo} actions={actions} activeKey={activeKey} defaultWidth={sidebarWidth} defaultCollapsed={defaultCollapsed}>
|
|
12
12
|
<nav aria-label="应用导航" className="oy-nav-menu">
|