codexmate 0.1.18 → 0.1.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -732,8 +732,8 @@
732
732
  this.invalidateSessionTimelineMeasurementCache();
733
733
  },
734
734
 
735
- async loadMoreSessionMessages(stepSize) {
736
- return loadMoreSessionMessagesHelper.call(this, stepSize);
735
+ async loadMoreSessionMessages() {
736
+ return loadMoreSessionMessagesHelper.call(this);
737
737
  },
738
738
 
739
739
  suspendSessionTabRender() {
@@ -268,6 +268,7 @@ const en = Object.freeze({
268
268
  'dashboard.doctor.export.json': 'Export JSON',
269
269
  'dashboard.doctor.export.md': 'Export Markdown',
270
270
  'dashboard.doctor.open': 'Open',
271
+ 'dashboard.doctor.empty': 'No checks have been run yet. Run checks to diagnose the current setup.',
271
272
  'doctor.action.openConfig': 'Open Config',
272
273
  'doctor.action.checkProvider': 'Check provider config',
273
274
  'doctor.action.openUsage': 'Open Usage',
@@ -270,6 +270,7 @@ const ja = Object.freeze({
270
270
  'dashboard.doctor.export.json': 'JSON エクスポート',
271
271
  'dashboard.doctor.export.md': 'Markdown エクスポート',
272
272
  'dashboard.doctor.open': '開く',
273
+ 'dashboard.doctor.empty': 'まだチェックは実行されていません。「チェックを実行」で現在のセットアップを診断します。',
273
274
  'doctor.action.openConfig': 'Config を開く',
274
275
  'doctor.action.checkProvider': 'Provider 設定をチェック',
275
276
  'doctor.action.openUsage': 'Usage を開く',
@@ -539,6 +539,7 @@ const vi = Object.freeze({
539
539
  'dashboard.doctor.export.json': 'Xuất JSON',
540
540
  'dashboard.doctor.export.md': 'Xuất Markdown',
541
541
  'dashboard.doctor.open': 'Mở',
542
+ 'dashboard.doctor.empty': 'Chưa chạy lần kiểm tra nào. Nhấn "Chạy kiểm tra" để chẩn đoán cấu hình hiện tại.',
542
543
  'doctor.action.openConfig': 'Mở cấu hình',
543
544
  'doctor.action.checkProvider': 'Kiểm tra cấu hình nhà cung cấp',
544
545
  'doctor.action.openUsage': 'Mở thống kê',
@@ -268,6 +268,7 @@ const zhTw = Object.freeze({
268
268
  'dashboard.doctor.export.json': '匯出 JSON',
269
269
  'dashboard.doctor.export.md': '匯出 Markdown',
270
270
  'dashboard.doctor.open': '打開',
271
+ 'dashboard.doctor.empty': '尚未執行檢查。點擊「運行檢查」診斷目前設定。',
271
272
  'doctor.action.openConfig': '打開 Config',
272
273
  'doctor.action.checkProvider': '檢查 Provider 設定',
273
274
  'doctor.action.openUsage': '打開 Usage',
@@ -268,6 +268,7 @@ const zh = Object.freeze({
268
268
  'dashboard.doctor.export.json': '导出 JSON',
269
269
  'dashboard.doctor.export.md': '导出 Markdown',
270
270
  'dashboard.doctor.open': '打开',
271
+ 'dashboard.doctor.empty': '尚未运行检查。点击上方“运行检查”诊断当前配置。',
271
272
  'doctor.action.openConfig': '打开 Config',
272
273
  'doctor.action.checkProvider': '检查 Provider 配置',
273
274
  'doctor.action.openUsage': '打开 Usage',
@@ -5,11 +5,11 @@
5
5
  role="tabpanel"
6
6
  :aria-labelledby="'tab-dashboard'">
7
7
  <div class="selector-section doctor-hero">
8
- <div class="selector-header">
8
+ <div class="selector-header doctor-header">
9
9
  <span class="selector-title">{{ t('dashboard.doctor.title') }}</span>
10
10
  <div class="selector-actions">
11
11
  <button
12
- class="btn-tool btn-tool-compact"
12
+ class="btn-tool btn-tool-compact doctor-run-btn"
13
13
  @click="runHealthCheck"
14
14
  :disabled="loading || !!initError || healthCheckLoading">
15
15
  {{ healthCheckLoading ? t('dashboard.doctor.checking') : t('dashboard.doctor.runChecks') }}
@@ -30,52 +30,59 @@
30
30
  <strong>{{ inspectorModelLoadStatus }}</strong>
31
31
  </div>
32
32
  </div>
33
- <div v-if="healthCheckResult" class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
34
- <div class="doctor-health-title">
35
- {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
36
- <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
37
- </div>
38
- </div>
39
- <div v-if="healthCheckResult && healthCheckResult.report" class="doctor-action-list">
40
- <template v-if="healthCheckResult.report.issues && healthCheckResult.report.issues.length">
41
- <div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
42
- <div class="doctor-action-head">
43
- <div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
44
- <div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
33
+ <div class="doctor-result-section">
34
+ <template v-if="healthCheckResult">
35
+ <div class="doctor-health-result" :class="healthCheckResult.ok ? 'ok' : 'error'">
36
+ <div class="doctor-health-title">
37
+ {{ healthCheckResult.ok ? t('dashboard.health.ok') : t('dashboard.health.fail') }}
38
+ <span v-if="healthCheckResult.issues && healthCheckResult.issues.length">({{ t('dashboard.health.issues', { count: healthCheckResult.issues.length }) }})</span>
45
39
  </div>
46
- <div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
47
- <div
48
- v-if="issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target)"
49
- class="doctor-action-buttons">
50
- <template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
51
- <button
52
- v-if="action.type === 'navigate' && action.target"
53
- type="button"
54
- class="btn-tool btn-tool-compact"
55
- @click="action.target ? switchMainTab(action.target) : null">
56
- {{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
57
- </button>
58
- </template>
40
+ </div>
41
+ <div v-if="healthCheckResult.report" class="doctor-action-list">
42
+ <template v-if="healthCheckResult.report.issues && healthCheckResult.report.issues.length">
43
+ <div v-for="issue in healthCheckResult.report.issues" :key="issue.id" class="doctor-action-card">
44
+ <div class="doctor-action-head">
45
+ <div class="doctor-action-title">{{ issue.problem || (issue.problemKey ? t(issue.problemKey, issue.problemParams) : '') }}</div>
46
+ <div :class="['doctor-action-severity', issue.severity]">{{ issue.severityLabel || issue.severity }}</div>
47
+ </div>
48
+ <div class="doctor-action-impact">{{ issue.impact || (issue.impactKey ? t(issue.impactKey, issue.impactParams) : '') }}</div>
49
+ <div
50
+ v-if="issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target)"
51
+ class="doctor-action-buttons">
52
+ <template v-for="(action, index) in issue.actions" :key="issue.id + '-action-' + index">
53
+ <button
54
+ v-if="action.type === 'navigate' && action.target"
55
+ type="button"
56
+ class="btn-tool btn-tool-compact"
57
+ @click="action.target ? switchMainTab(action.target) : null">
58
+ {{ action.label || (action.labelKey ? t(action.labelKey, action.labelParams) : t('dashboard.doctor.open')) }}
59
+ </button>
60
+ </template>
61
+ </div>
62
+ </div>
63
+ </template>
64
+ <div class="doctor-action-footer">
65
+ <button
66
+ type="button"
67
+ class="btn-tool btn-tool-compact"
68
+ @click="healthCheckResult && healthCheckResult.report
69
+ ? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
70
+ : null">
71
+ {{ t('dashboard.doctor.export.json') }}
72
+ </button>
73
+ <button
74
+ type="button"
75
+ class="btn-tool btn-tool-compact"
76
+ @click="healthCheckResult && healthCheckResult.report
77
+ ? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
78
+ : null">
79
+ {{ t('dashboard.doctor.export.md') }}
80
+ </button>
59
81
  </div>
60
82
  </div>
61
83
  </template>
62
- <div class="doctor-action-footer">
63
- <button
64
- type="button"
65
- class="btn-tool btn-tool-compact"
66
- @click="healthCheckResult && healthCheckResult.report
67
- ? downloadTextFile('codexmate-doctor.json', JSON.stringify(healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
68
- : null">
69
- {{ t('dashboard.doctor.export.json') }}
70
- </button>
71
- <button
72
- type="button"
73
- class="btn-tool btn-tool-compact"
74
- @click="healthCheckResult && healthCheckResult.report
75
- ? downloadTextFile('codexmate-doctor.md', String(healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
76
- : null">
77
- {{ t('dashboard.doctor.export.md') }}
78
- </button>
84
+ <div v-else class="doctor-result-empty">
85
+ <span class="doctor-result-empty-text">{{ t('dashboard.doctor.empty') }}</span>
79
86
  </div>
80
87
  </div>
81
88
  <div class="doctor-grid">
@@ -819,11 +819,11 @@ return function render(_ctx, _cache) {
819
819
  "aria-labelledby": 'tab-dashboard'
820
820
  }, [
821
821
  _createElementVNode("div", { class: "selector-section doctor-hero" }, [
822
- _createElementVNode("div", { class: "selector-header" }, [
822
+ _createElementVNode("div", { class: "selector-header doctor-header" }, [
823
823
  _createElementVNode("span", { class: "selector-title" }, _toDisplayString(_ctx.t('dashboard.doctor.title')), 1 /* TEXT */),
824
824
  _createElementVNode("div", { class: "selector-actions" }, [
825
825
  _createElementVNode("button", {
826
- class: "btn-tool btn-tool-compact",
826
+ class: "btn-tool btn-tool-compact doctor-run-btn",
827
827
  onClick: _ctx.runHealthCheck,
828
828
  disabled: _ctx.loading || !!_ctx.initError || _ctx.healthCheckLoading
829
829
  }, _toDisplayString(_ctx.healthCheckLoading ? _ctx.t('dashboard.doctor.checking') : _ctx.t('dashboard.doctor.runChecks')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])
@@ -845,79 +845,87 @@ return function render(_ctx, _cache) {
845
845
  _createElementVNode("strong", null, _toDisplayString(_ctx.inspectorModelLoadStatus), 1 /* TEXT */)
846
846
  ])
847
847
  ]),
848
- (_ctx.healthCheckResult)
849
- ? (_openBlock(), _createElementBlock("div", {
850
- key: 0,
851
- class: _normalizeClass(["doctor-health-result", _ctx.healthCheckResult.ok ? 'ok' : 'error'])
852
- }, [
853
- _createElementVNode("div", { class: "doctor-health-title" }, [
854
- _createTextVNode(_toDisplayString(_ctx.healthCheckResult.ok ? _ctx.t('dashboard.health.ok') : _ctx.t('dashboard.health.fail')) + " ", 1 /* TEXT */),
855
- (_ctx.healthCheckResult.issues && _ctx.healthCheckResult.issues.length)
856
- ? (_openBlock(), _createElementBlock("span", { key: 0 }, "(" + _toDisplayString(_ctx.t('dashboard.health.issues', { count: _ctx.healthCheckResult.issues.length })) + ")", 1 /* TEXT */))
857
- : _createCommentVNode("v-if", true)
858
- ])
859
- ], 2 /* CLASS */))
860
- : _createCommentVNode("v-if", true),
861
- (_ctx.healthCheckResult && _ctx.healthCheckResult.report)
862
- ? (_openBlock(), _createElementBlock("div", {
863
- key: 1,
864
- class: "doctor-action-list"
865
- }, [
866
- (_ctx.healthCheckResult.report.issues && _ctx.healthCheckResult.report.issues.length)
867
- ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.healthCheckResult.report.issues, (issue) => {
868
- return (_openBlock(), _createElementBlock("div", {
869
- key: issue.id,
870
- class: "doctor-action-card"
848
+ _createElementVNode("div", { class: "doctor-result-section" }, [
849
+ (_ctx.healthCheckResult)
850
+ ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [
851
+ _createElementVNode("div", {
852
+ class: _normalizeClass(["doctor-health-result", _ctx.healthCheckResult.ok ? 'ok' : 'error'])
853
+ }, [
854
+ _createElementVNode("div", { class: "doctor-health-title" }, [
855
+ _createTextVNode(_toDisplayString(_ctx.healthCheckResult.ok ? _ctx.t('dashboard.health.ok') : _ctx.t('dashboard.health.fail')) + " ", 1 /* TEXT */),
856
+ (_ctx.healthCheckResult.issues && _ctx.healthCheckResult.issues.length)
857
+ ? (_openBlock(), _createElementBlock("span", { key: 0 }, "(" + _toDisplayString(_ctx.t('dashboard.health.issues', { count: _ctx.healthCheckResult.issues.length })) + ")", 1 /* TEXT */))
858
+ : _createCommentVNode("v-if", true)
859
+ ])
860
+ ], 2 /* CLASS */),
861
+ (_ctx.healthCheckResult.report)
862
+ ? (_openBlock(), _createElementBlock("div", {
863
+ key: 0,
864
+ class: "doctor-action-list"
871
865
  }, [
872
- _createElementVNode("div", { class: "doctor-action-head" }, [
873
- _createElementVNode("div", { class: "doctor-action-title" }, _toDisplayString(issue.problem || (issue.problemKey ? _ctx.t(issue.problemKey, issue.problemParams) : '')), 1 /* TEXT */),
874
- _createElementVNode("div", {
875
- class: _normalizeClass(['doctor-action-severity', issue.severity])
876
- }, _toDisplayString(issue.severityLabel || issue.severity), 3 /* TEXT, CLASS */)
877
- ]),
878
- _createElementVNode("div", { class: "doctor-action-impact" }, _toDisplayString(issue.impact || (issue.impactKey ? _ctx.t(issue.impactKey, issue.impactParams) : '')), 1 /* TEXT */),
879
- (issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target))
880
- ? (_openBlock(), _createElementBlock("div", {
881
- key: 0,
882
- class: "doctor-action-buttons"
883
- }, [
884
- (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(issue.actions, (action, index) => {
885
- return (_openBlock(), _createElementBlock(_Fragment, {
886
- key: issue.id + '-action-' + index
887
- }, [
888
- (action.type === 'navigate' && action.target)
889
- ? (_openBlock(), _createElementBlock("button", {
890
- key: 0,
891
- type: "button",
892
- class: "btn-tool btn-tool-compact",
893
- onClick: $event => (action.target ? _ctx.switchMainTab(action.target) : null)
894
- }, _toDisplayString(action.label || (action.labelKey ? _ctx.t(action.labelKey, action.labelParams) : _ctx.t('dashboard.doctor.open'))), 9 /* TEXT, PROPS */, ["onClick"]))
895
- : _createCommentVNode("v-if", true)
896
- ], 64 /* STABLE_FRAGMENT */))
897
- }), 128 /* KEYED_FRAGMENT */))
898
- ]))
899
- : _createCommentVNode("v-if", true)
866
+ (_ctx.healthCheckResult.report.issues && _ctx.healthCheckResult.report.issues.length)
867
+ ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.healthCheckResult.report.issues, (issue) => {
868
+ return (_openBlock(), _createElementBlock("div", {
869
+ key: issue.id,
870
+ class: "doctor-action-card"
871
+ }, [
872
+ _createElementVNode("div", { class: "doctor-action-head" }, [
873
+ _createElementVNode("div", { class: "doctor-action-title" }, _toDisplayString(issue.problem || (issue.problemKey ? _ctx.t(issue.problemKey, issue.problemParams) : '')), 1 /* TEXT */),
874
+ _createElementVNode("div", {
875
+ class: _normalizeClass(['doctor-action-severity', issue.severity])
876
+ }, _toDisplayString(issue.severityLabel || issue.severity), 3 /* TEXT, CLASS */)
877
+ ]),
878
+ _createElementVNode("div", { class: "doctor-action-impact" }, _toDisplayString(issue.impact || (issue.impactKey ? _ctx.t(issue.impactKey, issue.impactParams) : '')), 1 /* TEXT */),
879
+ (issue.actions && issue.actions.some(action => action && action.type === 'navigate' && action.target))
880
+ ? (_openBlock(), _createElementBlock("div", {
881
+ key: 0,
882
+ class: "doctor-action-buttons"
883
+ }, [
884
+ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(issue.actions, (action, index) => {
885
+ return (_openBlock(), _createElementBlock(_Fragment, {
886
+ key: issue.id + '-action-' + index
887
+ }, [
888
+ (action.type === 'navigate' && action.target)
889
+ ? (_openBlock(), _createElementBlock("button", {
890
+ key: 0,
891
+ type: "button",
892
+ class: "btn-tool btn-tool-compact",
893
+ onClick: $event => (action.target ? _ctx.switchMainTab(action.target) : null)
894
+ }, _toDisplayString(action.label || (action.labelKey ? _ctx.t(action.labelKey, action.labelParams) : _ctx.t('dashboard.doctor.open'))), 9 /* TEXT, PROPS */, ["onClick"]))
895
+ : _createCommentVNode("v-if", true)
896
+ ], 64 /* STABLE_FRAGMENT */))
897
+ }), 128 /* KEYED_FRAGMENT */))
898
+ ]))
899
+ : _createCommentVNode("v-if", true)
900
+ ]))
901
+ }), 128 /* KEYED_FRAGMENT */))
902
+ : _createCommentVNode("v-if", true),
903
+ _createElementVNode("div", { class: "doctor-action-footer" }, [
904
+ _createElementVNode("button", {
905
+ type: "button",
906
+ class: "btn-tool btn-tool-compact",
907
+ onClick: $event => {_ctx.healthCheckResult && _ctx.healthCheckResult.report
908
+ ? _ctx.downloadTextFile('codexmate-doctor.json', JSON.stringify(_ctx.healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
909
+ : null}
910
+ }, _toDisplayString(_ctx.t('dashboard.doctor.export.json')), 9 /* TEXT, PROPS */, ["onClick"]),
911
+ _createElementVNode("button", {
912
+ type: "button",
913
+ class: "btn-tool btn-tool-compact",
914
+ onClick: $event => {_ctx.healthCheckResult && _ctx.healthCheckResult.report
915
+ ? _ctx.downloadTextFile('codexmate-doctor.md', String(_ctx.healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
916
+ : null}
917
+ }, _toDisplayString(_ctx.t('dashboard.doctor.export.md')), 9 /* TEXT, PROPS */, ["onClick"])
918
+ ])
900
919
  ]))
901
- }), 128 /* KEYED_FRAGMENT */))
902
- : _createCommentVNode("v-if", true),
903
- _createElementVNode("div", { class: "doctor-action-footer" }, [
904
- _createElementVNode("button", {
905
- type: "button",
906
- class: "btn-tool btn-tool-compact",
907
- onClick: $event => {_ctx.healthCheckResult && _ctx.healthCheckResult.report
908
- ? _ctx.downloadTextFile('codexmate-doctor.json', JSON.stringify(_ctx.healthCheckResult.report, null, 2), 'application/json;charset=utf-8')
909
- : null}
910
- }, _toDisplayString(_ctx.t('dashboard.doctor.export.json')), 9 /* TEXT, PROPS */, ["onClick"]),
911
- _createElementVNode("button", {
912
- type: "button",
913
- class: "btn-tool btn-tool-compact",
914
- onClick: $event => {_ctx.healthCheckResult && _ctx.healthCheckResult.report
915
- ? _ctx.downloadTextFile('codexmate-doctor.md', String(_ctx.healthCheckResult.markdown || ''), 'text/markdown;charset=utf-8')
916
- : null}
917
- }, _toDisplayString(_ctx.t('dashboard.doctor.export.md')), 9 /* TEXT, PROPS */, ["onClick"])
918
- ])
919
- ]))
920
- : _createCommentVNode("v-if", true),
920
+ : _createCommentVNode("v-if", true)
921
+ ], 64 /* STABLE_FRAGMENT */))
922
+ : (_openBlock(), _createElementBlock("div", {
923
+ key: 1,
924
+ class: "doctor-result-empty"
925
+ }, [
926
+ _createElementVNode("span", { class: "doctor-result-empty-text" }, _toDisplayString(_ctx.t('dashboard.doctor.empty')), 1 /* TEXT */)
927
+ ]))
928
+ ]),
921
929
  _createElementVNode("div", { class: "doctor-grid" }, [
922
930
  _createElementVNode("button", {
923
931
  type: "button",
@@ -530,7 +530,7 @@ export async function loadActiveSessionDetail(api, options = {}) {
530
530
  }
531
531
  }
532
532
 
533
- export async function loadMoreSessionMessages(stepSize) {
533
+ export async function loadMoreSessionMessages() {
534
534
  if (this.mainTab !== 'sessions' || !this.sessionPreviewRenderEnabled) {
535
535
  return;
536
536
  }
@@ -541,19 +541,6 @@ export async function loadMoreSessionMessages(stepSize) {
541
541
  this.sessionPreviewVisibleCount = 0;
542
542
  return;
543
543
  }
544
- const step = Number.isFinite(stepSize)
545
- ? Math.max(1, Math.floor(stepSize))
546
- : (Number.isFinite(this.sessionPreviewLoadStep)
547
- ? Math.max(1, Math.floor(this.sessionPreviewLoadStep))
548
- : 40);
549
- const current = Number.isFinite(this.sessionPreviewVisibleCount)
550
- ? Math.max(0, Math.floor(this.sessionPreviewVisibleCount))
551
- : 0;
552
- const targetVisible = current + step;
553
- if (targetVisible <= total) {
554
- this.sessionPreviewVisibleCount = Math.min(total, targetVisible);
555
- return;
556
- }
557
544
 
558
545
  this.sessionPreviewVisibleCount = total;
559
546
  if (this.sessionDetailLoading) {
@@ -573,24 +560,19 @@ export async function loadMoreSessionMessages(stepSize) {
573
560
  const currentLimit = Number.isFinite(currentLimitRaw)
574
561
  ? Math.max(1, Math.floor(currentLimitRaw))
575
562
  : Math.max(1, total);
576
- const fetchStep = Number.isFinite(this.sessionDetailFetchStep)
577
- ? Math.max(1, Math.floor(this.sessionDetailFetchStep))
578
- : 80;
579
563
  const limitCapRaw = Number(this.sessionDetailMessageLimitCap);
580
564
  const limitCap = Number.isFinite(limitCapRaw)
581
565
  ? Math.max(1, Math.floor(limitCapRaw))
582
566
  : 1000;
583
567
 
584
- let nextLimit = Math.max(currentLimit + fetchStep, targetVisible);
585
- if (totalKnown > total) {
586
- nextLimit = Math.min(nextLimit, totalKnown);
587
- }
588
- nextLimit = Math.min(nextLimit, limitCap);
568
+ const nextLimit = totalKnown > 0
569
+ ? Math.min(totalKnown, limitCap)
570
+ : limitCap;
589
571
  if (nextLimit <= currentLimit) {
590
572
  return;
591
573
  }
592
574
 
593
- this.sessionPreviewPendingVisibleCount = targetVisible;
575
+ this.sessionPreviewPendingVisibleCount = nextLimit;
594
576
  this.sessionDetailMessageLimit = nextLimit;
595
577
  this.sessionPreviewLoadingMore = true;
596
578
  try {
@@ -598,4 +580,11 @@ export async function loadMoreSessionMessages(stepSize) {
598
580
  } finally {
599
581
  this.sessionPreviewLoadingMore = false;
600
582
  }
583
+
584
+ const loadedTotal = Array.isArray(this.activeSessionMessages)
585
+ ? this.activeSessionMessages.length
586
+ : 0;
587
+ if (loadedTotal > this.sessionPreviewVisibleCount) {
588
+ this.sessionPreviewVisibleCount = loadedTotal;
589
+ }
601
590
  }
@@ -4,9 +4,10 @@
4
4
 
5
5
  .doctor-grid {
6
6
  display: grid;
7
- grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
7
+ grid-template-columns: repeat(4, minmax(0, 1fr));
8
8
  gap: 12px;
9
9
  margin-top: 12px;
10
+ align-items: stretch;
10
11
  }
11
12
 
12
13
  .doctor-card {
@@ -102,14 +103,16 @@
102
103
  display: inline-flex;
103
104
  align-items: center;
104
105
  gap: 8px;
105
- padding: 8px 10px;
106
+ min-width: 150px;
107
+ padding: 8px 12px;
106
108
  border-radius: 999px;
107
109
  border: 1px solid var(--color-border-soft);
108
110
  background: rgba(255, 255, 255, 0.78);
109
111
  font-size: var(--font-size-caption);
110
112
  color: var(--color-text-secondary);
111
- justify-content: center;
112
- text-align: center;
113
+ justify-content: space-between;
114
+ text-align: left;
115
+ font-variant-numeric: tabular-nums;
113
116
  }
114
117
 
115
118
  .doctor-status-chip strong {
@@ -162,27 +165,6 @@
162
165
  text-align: left;
163
166
  }
164
167
 
165
- .doctor-health-issues {
166
- display: flex;
167
- flex-direction: column;
168
- gap: 6px;
169
- font-size: var(--font-size-caption);
170
- align-items: center;
171
- }
172
-
173
- .doctor-health-issue {
174
- padding: 8px 10px;
175
- border-radius: 12px;
176
- border: 1px solid rgba(220, 95, 108, 0.2);
177
- background: rgba(255, 255, 255, 0.75);
178
- color: var(--color-text-secondary);
179
- overflow-wrap: anywhere;
180
- word-break: break-word;
181
- text-align: center;
182
- width: 100%;
183
- max-width: 720px;
184
- }
185
-
186
168
  .doctor-action-list {
187
169
  margin-top: 14px;
188
170
  display: grid;
@@ -212,8 +194,11 @@
212
194
  color: var(--color-text-primary);
213
195
  min-width: 0;
214
196
  overflow: hidden;
215
- text-overflow: ellipsis;
216
- white-space: nowrap;
197
+ display: -webkit-box;
198
+ -webkit-box-orient: vertical;
199
+ -webkit-line-clamp: 2;
200
+ overflow-wrap: anywhere;
201
+ word-break: break-word;
217
202
  max-width: 100%;
218
203
  flex: 1 1 220px;
219
204
  }
@@ -271,6 +256,46 @@
271
256
  margin-top: 2px;
272
257
  }
273
258
 
259
+ .doctor-result-section {
260
+ margin-top: 14px;
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 12px;
264
+ }
265
+
266
+ .doctor-result-section > .doctor-health-result,
267
+ .doctor-result-section > .doctor-action-list {
268
+ margin-top: 0;
269
+ }
270
+
271
+ .doctor-result-empty {
272
+ border: 1px dashed var(--color-border-soft);
273
+ border-radius: var(--radius-lg);
274
+ background: rgba(255, 255, 255, 0.5);
275
+ padding: 16px;
276
+ text-align: center;
277
+ font-size: var(--font-size-caption);
278
+ color: var(--color-text-tertiary);
279
+ }
280
+
281
+ .doctor-header .selector-actions > .doctor-run-btn {
282
+ background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
283
+ border-color: var(--color-brand-dark);
284
+ color: #fff;
285
+ }
286
+
287
+ .doctor-header .selector-actions > .doctor-run-btn:not(:disabled):hover {
288
+ border-color: var(--color-brand-dark);
289
+ color: #fff;
290
+ filter: brightness(1.05);
291
+ }
292
+
293
+ @media (max-width: 1280px) {
294
+ .doctor-grid {
295
+ grid-template-columns: repeat(2, minmax(0, 1fr));
296
+ }
297
+ }
298
+
274
299
  @media (max-width: 520px) {
275
300
  .doctor-grid {
276
301
  grid-template-columns: 1fr;
@@ -305,134 +330,3 @@
305
330
  }
306
331
  }
307
332
 
308
- .doctor-providers-health {
309
- margin-top: 12px;
310
- padding: 12px;
311
- border-radius: var(--radius-lg);
312
- border: 1px solid var(--color-border-soft);
313
- background: rgba(255, 255, 255, 0.78);
314
- }
315
-
316
- .doctor-providers-health-title {
317
- font-size: var(--font-size-body);
318
- font-weight: var(--font-weight-title);
319
- color: var(--color-text-primary);
320
- margin-bottom: 10px;
321
- display: flex;
322
- align-items: center;
323
- gap: 8px;
324
- }
325
-
326
- .doctor-providers-health-summary {
327
- font-size: var(--font-size-caption);
328
- font-weight: var(--font-weight-primary);
329
- color: var(--color-text-tertiary);
330
- }
331
-
332
- .doctor-providers-grid {
333
- display: flex;
334
- flex-wrap: wrap;
335
- gap: 8px;
336
- }
337
-
338
- .doctor-provider-chip {
339
- display: inline-flex;
340
- align-items: center;
341
- gap: 6px;
342
- padding: 6px 12px;
343
- border-radius: 999px;
344
- border: 1px solid var(--color-border-soft);
345
- background: rgba(255, 255, 255, 0.85);
346
- font-size: var(--font-size-caption);
347
- color: var(--color-text-secondary);
348
- max-width: 100%;
349
- overflow: hidden;
350
- }
351
-
352
- .doctor-provider-chip.green {
353
- border-color: rgba(57, 181, 97, 0.25);
354
- background: rgba(57, 181, 97, 0.08);
355
- color: #2b6a3b;
356
- }
357
-
358
- .doctor-provider-chip.yellow {
359
- border-color: rgba(246, 211, 106, 0.45);
360
- background: rgba(246, 211, 106, 0.16);
361
- color: #6f4b00;
362
- }
363
-
364
- .doctor-provider-chip.red {
365
- border-color: rgba(220, 95, 108, 0.28);
366
- background: rgba(220, 95, 108, 0.08);
367
- color: #8a2f36;
368
- }
369
-
370
- .doctor-provider-name {
371
- overflow: hidden;
372
- text-overflow: ellipsis;
373
- white-space: nowrap;
374
- }
375
-
376
- .doctor-provider-current {
377
- font-size: 0.85em;
378
- opacity: 0.7;
379
- margin-left: 2px;
380
- }
381
-
382
- .doctor-provider-status-dot {
383
- width: 8px;
384
- height: 8px;
385
- border-radius: 50%;
386
- flex-shrink: 0;
387
- }
388
-
389
- .doctor-provider-chip.green .doctor-provider-status-dot {
390
- background: rgba(57, 181, 97, 0.7);
391
- }
392
-
393
- .doctor-provider-chip.yellow .doctor-provider-status-dot {
394
- background: rgba(246, 211, 106, 0.85);
395
- }
396
-
397
- .doctor-provider-chip.red .doctor-provider-status-dot {
398
- background: rgba(220, 95, 108, 0.7);
399
- }
400
-
401
- .doctor-provider-issues {
402
- margin-top: 10px;
403
- display: flex;
404
- flex-direction: column;
405
- gap: 6px;
406
- }
407
-
408
- .doctor-provider-issue-card {
409
- padding: 8px 10px;
410
- border-radius: 10px;
411
- border: 1px solid rgba(220, 95, 108, 0.15);
412
- background: rgba(255, 255, 255, 0.7);
413
- }
414
-
415
- .doctor-provider-issue-name {
416
- font-weight: var(--font-weight-secondary);
417
- color: var(--color-text-primary);
418
- margin-bottom: 4px;
419
- }
420
-
421
- .doctor-provider-issue-row {
422
- font-size: var(--font-size-caption);
423
- color: var(--color-text-secondary);
424
- display: flex;
425
- flex-direction: column;
426
- gap: 2px;
427
- padding-left: 8px;
428
- margin-top: 4px;
429
- }
430
-
431
- .doctor-provider-issue-text {
432
- color: #8a2f36;
433
- }
434
-
435
- .doctor-provider-issue-fix {
436
- color: var(--color-text-tertiary);
437
- font-style: italic;
438
- }