@moon791017/neo-skills 1.0.30 → 1.0.33
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 +5 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -2
- package/skills/neo-agent-harness/SKILL.md +86 -0
- package/skills/neo-agent-harness/reference/agent-harness-engineering.md +343 -0
- package/skills/neo-agent-harness/reference/harness-patterns.md +180 -0
- package/skills/neo-dotnet-tag-helper/SKILL.md +0 -1
- package/skills/neo-dotnet-tag-helper/reference/coding-style.md +272 -168
package/README.md
CHANGED
|
@@ -63,7 +63,10 @@
|
|
|
63
63
|
### 10. 需求釐清助手
|
|
64
64
|
* **需求釐清**:系統化引導用戶釐清模糊需求,並將其轉化為結構化的規格文件(背景、功能、約束、驗收標準)。
|
|
65
65
|
|
|
66
|
-
### 11.
|
|
66
|
+
### 11. AI 開發流程健檢
|
|
67
|
+
* **AI 助手開發治理 (`skills/neo-agent-harness`)**:檢查專案規則、測試、CI、審查流程與安全防護是否足夠清楚,協助 AI 助手更穩定、更安全地參與開發。
|
|
68
|
+
|
|
69
|
+
### 12. 安全守衛 (Security Guard)
|
|
67
70
|
* **主動防護 (`secret-guard.ts`)**:作為 CLI 的中介軟體 (Hook),自動攔截並掃描所有工具執行的參數。若偵測到敏感資訊(如環境設定檔、私鑰、雲端憑證等)將強制阻擋執行,防止機密外洩。
|
|
68
71
|
|
|
69
72
|
## 📂 系統架構
|
|
@@ -215,6 +218,7 @@ npx -p @moon791017/neo-skills install-system-instructions \
|
|
|
215
218
|
| **全方位程式碼審查** | `幫我 code review 剛才的修改` |
|
|
216
219
|
| **生成 C# Interface** | `幫我針對這個 class 產生介面` |
|
|
217
220
|
| **技術解析與架構洞察** | `分析這個專案的架構` |
|
|
221
|
+
| **AI 開發流程健檢** | `幫我檢查這個專案怎麼讓 AI 助手開發得更穩、更安全` |
|
|
218
222
|
|
|
219
223
|
## 🛠 開發指南
|
|
220
224
|
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moon791017/neo-skills",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Neo Skills: A Universal Expert Agent Extension",
|
|
6
6
|
"homepage": "https://neo-blog-iota.vercel.app/",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"dist",
|
|
21
21
|
"bin",
|
|
22
22
|
"skills",
|
|
23
|
-
"commands",
|
|
24
23
|
"gemini-extension.json",
|
|
25
24
|
"GEMINI.md"
|
|
26
25
|
],
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: neo-agent-harness
|
|
3
|
+
description: 分析專案的 AI agent harnessability,設計 feedforward guides、feedback sensors、驗證流程與人類決策點,用於提升 agent 輔助開發品質。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Harness Architect Skill
|
|
7
|
+
|
|
8
|
+
## Trigger On
|
|
9
|
+
- The user asks to design or improve an AI agent development workflow.
|
|
10
|
+
- The user wants coding agents to be more reliable, safer, or easier to supervise.
|
|
11
|
+
- The user asks for AGENTS.md, skills, tests, CI, hooks, review loops, or project rules to be planned together.
|
|
12
|
+
- The task is about harness engineering, agent harnessability, feedback loops, or "humans on the loop".
|
|
13
|
+
|
|
14
|
+
## Core Principle
|
|
15
|
+
Treat agent-assisted development as a controlled engineering system. Do not only improve prompts; design the guides, sensors, verification steps, and human decision points that let agents work with higher confidence.
|
|
16
|
+
|
|
17
|
+
Use this skill for planning first. Do not modify files unless the user explicitly asks for implementation after the harness plan is clear.
|
|
18
|
+
|
|
19
|
+
## Perceive
|
|
20
|
+
1. Inspect the repository before asking questions:
|
|
21
|
+
- Project instructions: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `.github/copilot-instructions.md`.
|
|
22
|
+
- Skill or prompt assets: `skills/`, `.codex/skills/`, `.claude/skills/`, `.github/skills/`.
|
|
23
|
+
- Validation commands: `package.json`, `pyproject.toml`, `Makefile`, CI workflow files, build scripts.
|
|
24
|
+
- Safety and governance: hooks, linters, formatters, secret scanners, dependency scanners.
|
|
25
|
+
- Existing documentation: README, architecture docs, ADRs, testing docs, contribution docs.
|
|
26
|
+
2. Identify the project type, primary languages, current test surface, CI status, and release/deploy path.
|
|
27
|
+
3. Separate discoverable repository facts from product or team preferences that require user confirmation.
|
|
28
|
+
|
|
29
|
+
## Reason
|
|
30
|
+
Analyze the project through these dimensions:
|
|
31
|
+
|
|
32
|
+
1. **Feedforward guides**
|
|
33
|
+
- What should the agent know before editing?
|
|
34
|
+
- Examples: project rules, coding style, architecture boundaries, task decomposition, testing strategy, domain vocabulary, safe file ownership.
|
|
35
|
+
2. **Feedback sensors**
|
|
36
|
+
- What can check the agent's work after each change?
|
|
37
|
+
- Examples: typecheck, tests, lint, build, static analysis, security checks, architecture tests, review agents, smoke tests.
|
|
38
|
+
3. **Control type**
|
|
39
|
+
- Prefer computational controls for fast deterministic checks.
|
|
40
|
+
- Use inferential controls for semantic review, architecture judgment, test-quality review, and risk assessment.
|
|
41
|
+
4. **Regulation category**
|
|
42
|
+
- Maintainability: readability, duplication, complexity, style, testability.
|
|
43
|
+
- Architecture fitness: module boundaries, performance, observability, security, deployability.
|
|
44
|
+
- Behaviour: requirements, acceptance criteria, user journeys, fixtures, manual test points.
|
|
45
|
+
5. **Human role**
|
|
46
|
+
- Move human effort from repeated low-level correction to high-value decisions: scope, product fit, risk acceptance, architectural tradeoffs, and harness evolution.
|
|
47
|
+
|
|
48
|
+
For detailed patterns and examples, read [reference/harness-patterns.md](reference/harness-patterns.md) when the task needs a full harness design, maturity model, or improvement roadmap. For the complete source article synthesis behind this skill, read [reference/agent-harness-engineering.md](reference/agent-harness-engineering.md) only when deeper conceptual background is needed.
|
|
49
|
+
|
|
50
|
+
## Act
|
|
51
|
+
Output in Traditional Chinese (Taiwan). Use this structure:
|
|
52
|
+
|
|
53
|
+
### 1. 現況盤點
|
|
54
|
+
- Summarize the repository facts discovered.
|
|
55
|
+
- Mention the current guides, sensors, and missing signals.
|
|
56
|
+
|
|
57
|
+
### 2. Harnessability 評估
|
|
58
|
+
- Rate the current harnessability as `低`, `中`, or `高`.
|
|
59
|
+
- Explain the rating with concrete evidence from the repository.
|
|
60
|
+
|
|
61
|
+
### 3. Feedforward Guides 設計
|
|
62
|
+
- List the rules, docs, skills, templates, or examples agents should receive before work starts.
|
|
63
|
+
- Mark each item as existing, needs update, or missing.
|
|
64
|
+
|
|
65
|
+
### 4. Feedback Sensors 設計
|
|
66
|
+
- List fast local checks, CI checks, security checks, semantic reviews, and manual checks.
|
|
67
|
+
- Distinguish computational from inferential checks.
|
|
68
|
+
|
|
69
|
+
### 5. 開發前改善清單
|
|
70
|
+
- Prioritize improvements as P0, P1, and P2.
|
|
71
|
+
- P0 must focus on changes that reduce repeated agent mistakes or prevent high-risk failures.
|
|
72
|
+
|
|
73
|
+
### 6. 人類決策點
|
|
74
|
+
- State where humans should stay on the loop.
|
|
75
|
+
- Identify decisions that should not be delegated fully to agents.
|
|
76
|
+
|
|
77
|
+
### 7. 驗證方式
|
|
78
|
+
- Provide exact commands or review steps when discoverable.
|
|
79
|
+
- If a command is unknown, state the missing fact instead of inventing one.
|
|
80
|
+
|
|
81
|
+
## Constraints
|
|
82
|
+
- Do not treat AI-generated tests as sufficient proof of behaviour correctness.
|
|
83
|
+
- Do not replace deterministic tooling with LLM judgment when a fast tool can check the same thing.
|
|
84
|
+
- Do not recommend broad automation before the project has reliable local validation commands.
|
|
85
|
+
- Do not propose fully autonomous changes for security, compliance, production deploys, or product-scope decisions.
|
|
86
|
+
- Keep the output actionable and tied to repository evidence.
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Agent Harness Engineering and the Role of Humans in the Software Engineering Loop
|
|
2
|
+
|
|
3
|
+
> This document is a summary and integration of two articles from Martin Fowler's website, focusing on practical explanations rather than word-for-word translation.
|
|
4
|
+
|
|
5
|
+
## Original Sources
|
|
6
|
+
|
|
7
|
+
- [Harness engineering for coding agent users](https://martinfowler.com/articles/harness-engineering.html)
|
|
8
|
+
Author: Birgitta Boeckeler, Published: 2026-04-02
|
|
9
|
+
- [Humans and Agents in Software Engineering Loops](https://martinfowler.com/articles/exploring-gen-ai/humans-and-agents.html)
|
|
10
|
+
Author: Kief Morris, Published: 2026-03-04
|
|
11
|
+
|
|
12
|
+
## One-sentence Summary
|
|
13
|
+
|
|
14
|
+
The focus of AI coding agents is not just "letting the model write code," but establishing a working system (harness) for the agent to guide, check, correct, and continuously improve. Humans should not just review line-by-line at the lowest level, nor should they exit completely; instead, they should stand above the loop, designing and maintaining the agent's harness to ensure reliable software output in a controlled environment.
|
|
15
|
+
|
|
16
|
+
## Core Background
|
|
17
|
+
|
|
18
|
+
LLM coding agents can generate code quickly, but they have several inherent limitations:
|
|
19
|
+
|
|
20
|
+
- Output is not entirely predictable.
|
|
21
|
+
- They may not understand the full business context.
|
|
22
|
+
- They have limited grasp of existing code, team conventions, architectural constraints, and product trade-offs.
|
|
23
|
+
- They are prone to iterating in the wrong direction, wasting tokens, time, and review costs.
|
|
24
|
+
|
|
25
|
+
Therefore, the key to improving agent performance is not just switching to a stronger model, but designing the environment in which it works. This environment includes specifications, documentation, templates, tests, linting, architectural checks, review agents, CI pipelines, observability data, and human decision-making processes. This entire external control system is the "harness" discussed in the articles.
|
|
26
|
+
|
|
27
|
+
## What is a Harness?
|
|
28
|
+
|
|
29
|
+
In a broad sense, an agent can be understood as:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Agent = Model + Harness
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In the context of coding agents, the model is the LLM responsible for reasoning and generation; the harness is the working system wrapped around the model. It may include:
|
|
36
|
+
|
|
37
|
+
- System prompts, project rules, AGENTS.md, skills, and how-to guides.
|
|
38
|
+
- Code retrieval, language servers, CLI tools, and MCP servers.
|
|
39
|
+
- Tests, type checks, linting, static analysis, and architectural rules.
|
|
40
|
+
- AI code reviews, LLM judges, and semantic checks.
|
|
41
|
+
- CI/CD pipelines, production telemetry, and user journey data.
|
|
42
|
+
|
|
43
|
+
A good harness has two goals:
|
|
44
|
+
|
|
45
|
+
- Increase the probability of the agent getting it right the first time.
|
|
46
|
+
- Enable the agent to self-correct more issues based on feedback before reaching humans.
|
|
47
|
+
|
|
48
|
+
In other words, the value of a harness is to reduce the human review burden while improving system quality.
|
|
49
|
+
|
|
50
|
+
## Feedforward and Feedback
|
|
51
|
+
|
|
52
|
+
Harness controls can be divided into two categories.
|
|
53
|
+
|
|
54
|
+
### Feedforward: Guidance Before Action
|
|
55
|
+
|
|
56
|
+
Feedforward controls provide direction before the agent starts working, aiming to prevent errors. Examples include:
|
|
57
|
+
|
|
58
|
+
- Coding convention documents.
|
|
59
|
+
- Architectural principles.
|
|
60
|
+
- Task breakdown methods.
|
|
61
|
+
- Project bootstrap guides.
|
|
62
|
+
- API usage specifications.
|
|
63
|
+
- Technical decision records (ADRs).
|
|
64
|
+
- Templates and scaffolds.
|
|
65
|
+
- Codemods or fixed refactoring tools.
|
|
66
|
+
|
|
67
|
+
The effect of feedforward is to increase the probability that the agent produces correct results from the start.
|
|
68
|
+
|
|
69
|
+
### Feedback: Sensing and Correction After Action
|
|
70
|
+
|
|
71
|
+
Feedback controls provide signals after the agent performs an action, allowing it to check and self-correct. Examples include:
|
|
72
|
+
|
|
73
|
+
- Unit and integration tests.
|
|
74
|
+
- Type checkers.
|
|
75
|
+
- Linters.
|
|
76
|
+
- Architecture fitness tests.
|
|
77
|
+
- Coverage or mutation testing.
|
|
78
|
+
- Semgrep or security scans.
|
|
79
|
+
- AI review agents.
|
|
80
|
+
- Browser smoke tests.
|
|
81
|
+
- Production logs, SLOs, error rates, and latency.
|
|
82
|
+
|
|
83
|
+
The effect of feedback is to ensure the agent is not just "constrained by rules" but knows whether its output is actually effective.
|
|
84
|
+
|
|
85
|
+
Without feedback, the agent might repeat the same mistakes; without feedforward, the team won't know if the rules are actually having an effect. A good harness requires both.
|
|
86
|
+
|
|
87
|
+
## Computational and Inferential
|
|
88
|
+
|
|
89
|
+
The articles also divide guides and sensors into two execution types.
|
|
90
|
+
|
|
91
|
+
### Computational Controls
|
|
92
|
+
|
|
93
|
+
Computational controls are deterministic, fast, and cheap machine checks, usually executed by the CPU. Examples include:
|
|
94
|
+
|
|
95
|
+
- Type checks.
|
|
96
|
+
- Unit tests.
|
|
97
|
+
- Linting.
|
|
98
|
+
- Static analysis.
|
|
99
|
+
- Structural tests.
|
|
100
|
+
- Codemods.
|
|
101
|
+
- Dependency checks.
|
|
102
|
+
|
|
103
|
+
The advantages of these controls are stability, low cost, and repeatability, making them suitable for execution before every change, commit, or in the CI pipeline.
|
|
104
|
+
|
|
105
|
+
### Inferential Controls
|
|
106
|
+
|
|
107
|
+
Inferential controls are AI or LLM-based checks that require semantic judgment. Examples include:
|
|
108
|
+
|
|
109
|
+
- AI code reviews.
|
|
110
|
+
- LLM-as-a-judge.
|
|
111
|
+
- Commentary on architectural soundness.
|
|
112
|
+
- Commentary on test quality.
|
|
113
|
+
- Detection of duplicate logic or over-engineering.
|
|
114
|
+
- Inferring potential issues from logs.
|
|
115
|
+
|
|
116
|
+
These controls can handle problems that traditional tools find difficult, but they are more expensive, slower, and less stable. They are suitable for high-value, high-risk, or late-stage checks, rather than replacing all deterministic checks.
|
|
117
|
+
|
|
118
|
+
## Three Types of Harnesses
|
|
119
|
+
|
|
120
|
+
### 1. Maintainability Harness
|
|
121
|
+
|
|
122
|
+
The maintainability harness is used to maintain internal code quality. This is currently the easiest type to implement because existing tools are very mature.
|
|
123
|
+
|
|
124
|
+
Issues covered include:
|
|
125
|
+
|
|
126
|
+
- Duplicate code.
|
|
127
|
+
- Excessive complexity.
|
|
128
|
+
- Missing tests.
|
|
129
|
+
- Inconsistent naming or formatting.
|
|
130
|
+
- Violated architectural boundaries.
|
|
131
|
+
- Style guide violations.
|
|
132
|
+
|
|
133
|
+
Computational sensors are very effective for these issues. Inferential sensors can complement them at the semantic level, such as detecting "logic that looks different but is actually duplicate," "tests that exist but don't verify the core point," or "fixes that are too brute-force or over-engineered."
|
|
134
|
+
|
|
135
|
+
However, a maintainability harness cannot fully solve requirement misunderstandings, incorrect diagnoses, or scope creep. These still require clearer specifications and human judgment.
|
|
136
|
+
|
|
137
|
+
### 2. Architecture Fitness Harness
|
|
138
|
+
|
|
139
|
+
The architecture fitness harness is used to maintain architectural characteristics and system quality attributes. Examples include:
|
|
140
|
+
|
|
141
|
+
- Module boundaries.
|
|
142
|
+
- Consistency in API design.
|
|
143
|
+
- Performance requirements.
|
|
144
|
+
- Observability standards.
|
|
145
|
+
- Security and compliance requirements.
|
|
146
|
+
- Deployment and operational constraints.
|
|
147
|
+
|
|
148
|
+
Its feedforward might be architectural documents, ADRs, logging standards, or performance budgets; feedback might be architecture tests, performance tests, contract tests, observability reviews, or deployment checks.
|
|
149
|
+
|
|
150
|
+
The focus of this harness is to translate "what our system looks like" into rules that an agent can read, follow, and be checked against by tools.
|
|
151
|
+
|
|
152
|
+
### 3. Behaviour Harness
|
|
153
|
+
|
|
154
|
+
The behavior harness is used to confirm that software functional behavior meets requirements. This is currently the most difficult type.
|
|
155
|
+
|
|
156
|
+
Common practices include:
|
|
157
|
+
|
|
158
|
+
- Feedforward: Providing the agent with functional specifications, user stories, and acceptance criteria.
|
|
159
|
+
- Feedback: Letting the agent generate and execute tests, supplemented by coverage, mutation testing, or manual testing.
|
|
160
|
+
|
|
161
|
+
The problem is that AI-generated tests themselves are not necessarily trustworthy. Tests might only verify implementation details rather than actual requirements. The articles mention "approved fixtures" as a pattern that can improve test quality in some scenarios, but it's not a universal solution for all systems.
|
|
162
|
+
|
|
163
|
+
Therefore, functional correctness remains the area that most needs human product judgment and high-quality specifications.
|
|
164
|
+
|
|
165
|
+
## Harnessability: Not All Systems Are Equally Easy to Harness
|
|
166
|
+
|
|
167
|
+
Whether a codebase can be stably operated by an agent depends on whether it possesses enough structural clues.
|
|
168
|
+
|
|
169
|
+
Systems that are easier to harness usually have:
|
|
170
|
+
|
|
171
|
+
- Strongly typed languages and strict type checking.
|
|
172
|
+
- Clear module boundaries.
|
|
173
|
+
- Consistent frameworks and directory structures.
|
|
174
|
+
- Automatable tests.
|
|
175
|
+
- Clear coding conventions.
|
|
176
|
+
- Architectural rules checkable by tools.
|
|
177
|
+
|
|
178
|
+
Systems that are harder to harness usually have:
|
|
179
|
+
|
|
180
|
+
- High technical debt.
|
|
181
|
+
- Too many implicit rules.
|
|
182
|
+
- Vague architectural boundaries.
|
|
183
|
+
- Insufficient tests.
|
|
184
|
+
- A requirement for significant tribal knowledge.
|
|
185
|
+
- Highly mixed frameworks and styles.
|
|
186
|
+
|
|
187
|
+
This means greenfield projects can be designed for harnessability from day one, while legacy projects need to add structure, tests, and rules before safely increasing agent autonomy.
|
|
188
|
+
|
|
189
|
+
## Harness Templates
|
|
190
|
+
|
|
191
|
+
Large organizations often have several common types of services, such as:
|
|
192
|
+
|
|
193
|
+
- CRUD business services.
|
|
194
|
+
- Event processors.
|
|
195
|
+
- Data dashboards.
|
|
196
|
+
- API gateways.
|
|
197
|
+
- Batch jobs.
|
|
198
|
+
|
|
199
|
+
If these types already have service templates, they can evolve into harness templates. That is, each service type comes built-in with:
|
|
200
|
+
|
|
201
|
+
- Standard directory structure.
|
|
202
|
+
- Tech stack and dependencies.
|
|
203
|
+
- Generation and modification guides.
|
|
204
|
+
- Test templates.
|
|
205
|
+
- Architectural rules.
|
|
206
|
+
- CI pipelines.
|
|
207
|
+
- Security and observability checks.
|
|
208
|
+
- Agent review guides.
|
|
209
|
+
|
|
210
|
+
This narrows the space in which the agent can arbitrarily generate, making control more feasible. The downside is that the template itself requires version management, synchronization, and governance; otherwise, projects will gradually diverge from the upstream standard.
|
|
211
|
+
|
|
212
|
+
## Three Human Positions in the Loop
|
|
213
|
+
|
|
214
|
+
The second article provides another important model: where should humans stand in the loop?
|
|
215
|
+
|
|
216
|
+
### Humans Outside the Loop
|
|
217
|
+
|
|
218
|
+
This leaves humans in the "why loop" and lets the agent handle the "how loop." Humans only describe the desired result, and the agent decides how to construct the software.
|
|
219
|
+
|
|
220
|
+
This approach is close to what's often called "vibe coding." Its appeal lies in speed and minimal intervention, but the risk is that internal quality, maintainability, cost, security, and compliance may spiral out of control.
|
|
221
|
+
|
|
222
|
+
The articles remind us: we care about internal quality not because code is sacred, but because internal quality affects external outcomes. A chaotic codebase makes agents slower, more likely to get lost, and harder to modify reliably.
|
|
223
|
+
|
|
224
|
+
### Humans In the Loop
|
|
225
|
+
|
|
226
|
+
This keeps humans in the lowest-level coding loop, reviewing the agent's code line-by-line.
|
|
227
|
+
|
|
228
|
+
This approach leverages human experience and judgment, especially when the agent gets stuck, misunderstands, or repeatedly fixes things incorrectly. The problem is that humans become the bottleneck. Agents generate code much faster than humans can review it line-by-line. If all quality assurance relies on manual review, the efficiency gains of AI are neutralized.
|
|
229
|
+
|
|
230
|
+
### Humans On the Loop
|
|
231
|
+
|
|
232
|
+
The articles advocate for "humans on the loop": humans do not control the agent line-by-line, nor do they exit completely; instead, they design, manage, and improve the agent's work loop.
|
|
233
|
+
|
|
234
|
+
The difference can be understood as:
|
|
235
|
+
|
|
236
|
+
- In the loop: Seeing an incorrect agent output and fixing it directly, or telling the agent to fix that specific output.
|
|
237
|
+
- On the loop: Seeing an incorrect agent output and going back to modify the harness so that future similar outputs are more likely to be correct.
|
|
238
|
+
|
|
239
|
+
This turns every error into an opportunity to improve the system, rather than just fixing a one-off problem.
|
|
240
|
+
|
|
241
|
+
## Agentic Flywheel
|
|
242
|
+
|
|
243
|
+
A more advanced state is the "agentic flywheel": humans not only ask the agent to build features but also ask it to analyze the harness's effectiveness and propose improvements.
|
|
244
|
+
|
|
245
|
+
Implementation can start simply:
|
|
246
|
+
|
|
247
|
+
1. Let the agent read tests, CI results, and review findings.
|
|
248
|
+
2. Ask the agent to identify failure patterns and recurring issues.
|
|
249
|
+
3. Have the agent suggest adding or modifying rules, tests, documentation, linting, or pipelines.
|
|
250
|
+
4. Humans review these suggestions before assigning the agent to implement them.
|
|
251
|
+
5. As trust increases, let the agent flag risks, costs, and benefits for its suggestions.
|
|
252
|
+
6. Gradually automate low-risk, high-confidence improvements.
|
|
253
|
+
|
|
254
|
+
Such a flywheel doesn't aim for a one-time "it mostly works," but rather makes the system progressively more capable of self-checking, self-correcting, and self-improving.
|
|
255
|
+
|
|
256
|
+
## Practical Implications for Engineering Teams
|
|
257
|
+
|
|
258
|
+
### 1. Don't Understand AI Engineering Only as Prompting Skills
|
|
259
|
+
|
|
260
|
+
Prompts are important, but long-term reliability comes from the entire work system. Teams should view AGENTS.md, skills, specification documents, tests, linting, CI, review processes, and production feedback as a single harness rather than fragmented tools.
|
|
261
|
+
|
|
262
|
+
### 2. Prioritize Moving Cheap, Stable, and Deterministic Checks Upstream
|
|
263
|
+
|
|
264
|
+
Problems that can be solved with deterministic tooling should not be prioritized for LLM judgment. Type checks, linting, unit tests, formatting, and architectural rules should run as early as possible, ideally during the agent's self-correction phase.
|
|
265
|
+
|
|
266
|
+
### 3. Focus Human Attention on High-Value Judgments
|
|
267
|
+
|
|
268
|
+
The most valuable place for humans is not reading every line of code, but judging:
|
|
269
|
+
|
|
270
|
+
- Whether requirements are clear.
|
|
271
|
+
- Whether the product direction is correct.
|
|
272
|
+
- Whether architectural trade-offs are reasonable.
|
|
273
|
+
- Whether risks are acceptable.
|
|
274
|
+
- Which technical debts are intentional and which are out of control.
|
|
275
|
+
- How the harness should evolve.
|
|
276
|
+
|
|
277
|
+
### 4. Turn Recurring Errors into Harness Improvements
|
|
278
|
+
|
|
279
|
+
If an agent makes the same mistake multiple times, it shouldn't just be fixed each time it happens. Instead, ask:
|
|
280
|
+
|
|
281
|
+
- Is a feedforward document missing?
|
|
282
|
+
- Do tests need to be added?
|
|
283
|
+
- Can a lint rule or static analysis be added?
|
|
284
|
+
- Does AGENTS.md or a skill need to be updated?
|
|
285
|
+
- Does a project-specific review checklist need to be created?
|
|
286
|
+
- Can manual knowledge be turned into an automated check?
|
|
287
|
+
|
|
288
|
+
### 5. Be Cautious About Functional Behavior
|
|
289
|
+
|
|
290
|
+
Functional correctness is the hardest to automate fully. AI-generated tests cannot be equated directly with trustworthy acceptance. Important features still need clear specifications, acceptance criteria, manual exploratory testing, and, when necessary, domain expert reviews.
|
|
291
|
+
|
|
292
|
+
## Checklist for Building a Coding Agent Harness
|
|
293
|
+
|
|
294
|
+
### Phase 1: Establishing Basic Controllability
|
|
295
|
+
|
|
296
|
+
- Create or update AGENTS.md, explaining project rules, workflows, testing methods, and security limits.
|
|
297
|
+
- Ensure the project has one-click commands for install, test, and build.
|
|
298
|
+
- Set up type checking, linting, formatting, and unit tests.
|
|
299
|
+
- Document common task workflows in how-to guides or skills.
|
|
300
|
+
- Ensure the agent clearly knows which files can be modified and which should not be changed manually.
|
|
301
|
+
|
|
302
|
+
### Phase 2: Strengthening the Feedback Loop
|
|
303
|
+
|
|
304
|
+
- Automatically execute fast tests and type checks after agent modifications.
|
|
305
|
+
- Provide clear correction guidance for common failure messages.
|
|
306
|
+
- Add architectural boundary checks or dependency rules.
|
|
307
|
+
- Add contract tests or approval tests for important modules.
|
|
308
|
+
- Feed CI results back into the agent's correction process.
|
|
309
|
+
|
|
310
|
+
### Phase 3: Adding Semantic-Level Checks
|
|
311
|
+
|
|
312
|
+
- Establish a code review skill or checklist.
|
|
313
|
+
- Create specialized review processes for architecture, security, performance, and test quality.
|
|
314
|
+
- Let AI reviews find high-risk issues first, rather than replacing all manual reviews.
|
|
315
|
+
- Periodically review false positives and false negatives from AI reviews.
|
|
316
|
+
|
|
317
|
+
### Phase 4: Establishing the Harness Improvement Loop
|
|
318
|
+
|
|
319
|
+
- Collect common error patterns from the agent.
|
|
320
|
+
- Turn recurring errors into rules, tests, documentation, or tools.
|
|
321
|
+
- Periodically check that AGENTS.md, skills, CI, and linting are consistent with each other.
|
|
322
|
+
- Have the agent suggest harness improvements based on CI, reviews, and production data.
|
|
323
|
+
- Flag risks, costs, benefits, and automation levels for improvement suggestions.
|
|
324
|
+
|
|
325
|
+
## Observations Applicable to This Project
|
|
326
|
+
|
|
327
|
+
This repository itself is a collection of skills and agent work rules, making it highly suitable for management from a harness engineering perspective.
|
|
328
|
+
|
|
329
|
+
Consider the following directions:
|
|
330
|
+
|
|
331
|
+
- Treat each skill as a feedforward guide: it tells the agent how to act in a specific task.
|
|
332
|
+
- Treat `test/*.test.js`, `bun run typecheck`, and `bun run build` as computational feedback sensors.
|
|
333
|
+
- Treat `src/hooks/secret-guard.ts` and `hooks/hooks.json` as part of the security and governance harness.
|
|
334
|
+
- In the future, add specialized checks for skill quality, such as SKILL.md structure, required sections, valid reference paths, and template consistency.
|
|
335
|
+
- Establish a "skill review checklist" to assist in checking if instructions are clear, too broad, or in conflict with other skills using inferential review.
|
|
336
|
+
|
|
337
|
+
## Most Important Conclusion
|
|
338
|
+
|
|
339
|
+
The reliability of an AI agent is not produced naturally by model capability alone; it is formed by the combination of the model, context, tools, checks, feedback, and human governance.
|
|
340
|
+
|
|
341
|
+
The human role should not be simplified into two extremes: either exiting completely or guarding every line. A more sustainable approach is to stand above the loop, continuously designing and improving the system in which the agent works.
|
|
342
|
+
|
|
343
|
+
For teams actually delivering products, harness engineering will become a core engineering capability: gradually transforming human experience, team conventions, architectural judgment, and quality standards into a work environment that an agent can follow, execute, check, and continuously improve.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Agent Harness Patterns
|
|
2
|
+
|
|
3
|
+
Use this reference when designing a complete agent harness plan or roadmap.
|
|
4
|
+
|
|
5
|
+
## Core Model
|
|
6
|
+
|
|
7
|
+
An agent harness is the system around the model that guides, checks, and improves agent work.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Agent = Model + Harness
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For coding agents, the useful outer harness includes:
|
|
14
|
+
|
|
15
|
+
- Project instructions and task workflows.
|
|
16
|
+
- Skills, templates, examples, and architectural rules.
|
|
17
|
+
- Local verification commands and CI stages.
|
|
18
|
+
- Hooks, linters, static analysis, and security checks.
|
|
19
|
+
- AI review, architecture review, test-quality review, and human decision points.
|
|
20
|
+
- Production feedback such as logs, SLOs, user journeys, and support signals.
|
|
21
|
+
|
|
22
|
+
The goal is not full human removal. The goal is to reduce repeated review toil and direct human attention to product, risk, architecture, and tradeoff decisions.
|
|
23
|
+
|
|
24
|
+
## Feedforward Guides
|
|
25
|
+
|
|
26
|
+
Feedforward guides shape agent output before it acts.
|
|
27
|
+
|
|
28
|
+
Common examples:
|
|
29
|
+
|
|
30
|
+
- `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, Copilot instructions.
|
|
31
|
+
- Coding standards and style references.
|
|
32
|
+
- Architecture docs, ADRs, dependency rules, module maps.
|
|
33
|
+
- Task-specific skills and how-to guides.
|
|
34
|
+
- Service templates, scaffolds, fixtures, and examples.
|
|
35
|
+
- API docs, domain vocabulary, data contracts, and non-functional requirements.
|
|
36
|
+
|
|
37
|
+
Good feedforward guides are:
|
|
38
|
+
|
|
39
|
+
- Specific enough to prevent repeated mistakes.
|
|
40
|
+
- Short enough to be loaded often.
|
|
41
|
+
- Close to the code they govern.
|
|
42
|
+
- Kept in sync with tests, CI, and review expectations.
|
|
43
|
+
|
|
44
|
+
## Feedback Sensors
|
|
45
|
+
|
|
46
|
+
Feedback sensors observe work after the agent acts and provide correction signals.
|
|
47
|
+
|
|
48
|
+
Fast computational sensors:
|
|
49
|
+
|
|
50
|
+
- Typecheck.
|
|
51
|
+
- Unit tests and focused integration tests.
|
|
52
|
+
- Lint and format checks.
|
|
53
|
+
- Build checks.
|
|
54
|
+
- Static analysis.
|
|
55
|
+
- Dependency and secret scanning.
|
|
56
|
+
- Architecture boundary tests.
|
|
57
|
+
|
|
58
|
+
Slower or inferential sensors:
|
|
59
|
+
|
|
60
|
+
- AI code review.
|
|
61
|
+
- Architecture review.
|
|
62
|
+
- Test-quality review.
|
|
63
|
+
- Security reasoning review.
|
|
64
|
+
- Log and incident analysis.
|
|
65
|
+
- Behaviour or UX review against user journeys.
|
|
66
|
+
|
|
67
|
+
Prefer fast deterministic sensors during local development. Reserve inferential sensors for semantic judgment, riskier changes, and later review stages.
|
|
68
|
+
|
|
69
|
+
## Regulation Categories
|
|
70
|
+
|
|
71
|
+
### Maintainability Harness
|
|
72
|
+
|
|
73
|
+
Purpose: keep the codebase easy for humans and agents to understand and change.
|
|
74
|
+
|
|
75
|
+
Signals:
|
|
76
|
+
|
|
77
|
+
- Duplicate code.
|
|
78
|
+
- Excessive complexity.
|
|
79
|
+
- Naming or style drift.
|
|
80
|
+
- Missing or weak tests.
|
|
81
|
+
- Hard-to-review diffs.
|
|
82
|
+
- Over-engineered or brute-force fixes.
|
|
83
|
+
|
|
84
|
+
Useful controls:
|
|
85
|
+
|
|
86
|
+
- Format, lint, typecheck, tests.
|
|
87
|
+
- Complexity and dependency analysis.
|
|
88
|
+
- Code review skill.
|
|
89
|
+
- Refactoring guides and examples.
|
|
90
|
+
|
|
91
|
+
### Architecture Fitness Harness
|
|
92
|
+
|
|
93
|
+
Purpose: keep the system aligned with its intended structure and quality attributes.
|
|
94
|
+
|
|
95
|
+
Signals:
|
|
96
|
+
|
|
97
|
+
- Module boundary violations.
|
|
98
|
+
- API inconsistency.
|
|
99
|
+
- Performance regression.
|
|
100
|
+
- Observability gaps.
|
|
101
|
+
- Security or compliance drift.
|
|
102
|
+
- Deployment fragility.
|
|
103
|
+
|
|
104
|
+
Useful controls:
|
|
105
|
+
|
|
106
|
+
- Architecture docs and ADRs.
|
|
107
|
+
- Fitness functions and structural tests.
|
|
108
|
+
- Contract tests.
|
|
109
|
+
- Performance budgets.
|
|
110
|
+
- Logging and tracing standards.
|
|
111
|
+
- CI gates for risky paths.
|
|
112
|
+
|
|
113
|
+
### Behaviour Harness
|
|
114
|
+
|
|
115
|
+
Purpose: verify that the product does what stakeholders need.
|
|
116
|
+
|
|
117
|
+
Signals:
|
|
118
|
+
|
|
119
|
+
- Ambiguous requirements.
|
|
120
|
+
- Tests that only mirror implementation.
|
|
121
|
+
- Missing acceptance criteria.
|
|
122
|
+
- Broken user journeys.
|
|
123
|
+
- Manual test steps that are not captured.
|
|
124
|
+
|
|
125
|
+
Useful controls:
|
|
126
|
+
|
|
127
|
+
- User stories with acceptance criteria.
|
|
128
|
+
- Approved fixtures or golden examples where appropriate.
|
|
129
|
+
- End-to-end smoke tests for critical flows.
|
|
130
|
+
- Domain expert review for high-impact behaviour.
|
|
131
|
+
- Manual exploration checklist when automation is weak.
|
|
132
|
+
|
|
133
|
+
Do not assume AI-generated tests prove behaviour correctness. Behaviour harnesses need clear human intent and high-quality examples.
|
|
134
|
+
|
|
135
|
+
## Harnessability Assessment
|
|
136
|
+
|
|
137
|
+
Rate harnessability by evidence:
|
|
138
|
+
|
|
139
|
+
- **High**: clear structure, strong typing, reliable tests, CI, documented rules, repeatable build, clear ownership.
|
|
140
|
+
- **Medium**: some tests and rules exist, but coverage, architecture docs, or validation commands are incomplete.
|
|
141
|
+
- **Low**: weak tests, unclear structure, hidden conventions, inconsistent frameworks, no reliable local checks, or high technical debt.
|
|
142
|
+
|
|
143
|
+
Common improvement sequence:
|
|
144
|
+
|
|
145
|
+
1. Make setup, test, typecheck, and build commands explicit.
|
|
146
|
+
2. Document project rules in the agent instruction file.
|
|
147
|
+
3. Add fast computational checks before adding AI review.
|
|
148
|
+
4. Add architecture or domain-specific sensors for repeated failures.
|
|
149
|
+
5. Add inferential review for semantic quality and risk.
|
|
150
|
+
6. Feed recurring failures back into guides, tests, hooks, or skills.
|
|
151
|
+
|
|
152
|
+
## Maturity Roadmap
|
|
153
|
+
|
|
154
|
+
### Level 1: Basic Control
|
|
155
|
+
|
|
156
|
+
- Agent can find project rules.
|
|
157
|
+
- Local validation commands are documented.
|
|
158
|
+
- Secret and destructive-action rules are clear.
|
|
159
|
+
- Human reviews all significant changes.
|
|
160
|
+
|
|
161
|
+
### Level 2: Fast Self-Correction
|
|
162
|
+
|
|
163
|
+
- Agent runs focused tests, typecheck, lint, and build after changes.
|
|
164
|
+
- Failure messages are actionable.
|
|
165
|
+
- Repeated mistakes become guides or checks.
|
|
166
|
+
- CI repeats the local quality gates.
|
|
167
|
+
|
|
168
|
+
### Level 3: Architecture-Aware Development
|
|
169
|
+
|
|
170
|
+
- Agent understands module boundaries and service topology.
|
|
171
|
+
- Architecture fitness checks catch drift.
|
|
172
|
+
- Review process checks performance, observability, and deployability.
|
|
173
|
+
- Templates encode common project shapes.
|
|
174
|
+
|
|
175
|
+
### Level 4: Harness Improvement Loop
|
|
176
|
+
|
|
177
|
+
- Agent reviews CI, review comments, and incidents to propose harness improvements.
|
|
178
|
+
- Humans prioritize and approve harness changes.
|
|
179
|
+
- Low-risk improvements can be automated after repeated success.
|
|
180
|
+
- Harness quality itself is reviewed regularly.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# .NET Tag Helper Coding Style & Advanced Patterns
|
|
2
2
|
|
|
3
|
-
This guide defines advanced coding styles and implementation patterns for ASP.NET Core Tag Helpers, specifically targeting complex UI components, extending built-in behaviors, and frontend asset management mechanisms.
|
|
3
|
+
This guide defines advanced coding styles and implementation patterns for ASP.NET Core Tag Helpers, specifically targeting complex UI components, extending built-in behaviors, and frontend asset management mechanisms. **When developing Tag Helpers, developers MUST refer to the implementation patterns in the Example section (Section 3) to ensure adherence to these standards.**
|
|
4
4
|
|
|
5
5
|
## 1. Inheriting and Extending Built-in Tag Helpers
|
|
6
6
|
|
|
@@ -11,7 +11,101 @@ When you need to customize standard form elements (e.g., `<select>`), you should
|
|
|
11
11
|
- You must explicitly set `output.TagName`; otherwise, the browser will output tags with custom names (e.g., `<select-ai-agent>`).
|
|
12
12
|
- Call `await base.ProcessAsync(context, output)` to allow the base class to handle native attribute binding.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## 2. Mandatory Custom Tag Helper Properties & Asset Management
|
|
15
|
+
|
|
16
|
+
To ensure consistency, performance, and maintainability across all custom UI components, every custom Tag Helper MUST implement the following properties and logic.
|
|
17
|
+
|
|
18
|
+
### 2.1 Mandatory Properties
|
|
19
|
+
|
|
20
|
+
1. **`asp-for` (Model Binding)**:
|
|
21
|
+
Support strong typing by including a `ModelExpression` property. This is crucial for retrieving model metadata (Name, Id, Value, Validation) and generating safe HTML identifiers.
|
|
22
|
+
```csharp
|
|
23
|
+
[HtmlAttributeName("asp-for")]
|
|
24
|
+
public ModelExpression For { get; set; } = null!;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
2. **CSS Class Handling (UI Class)**:
|
|
28
|
+
Tag Helpers must provide a default CSS class. If the user provides a custom class in the HTML tag:
|
|
29
|
+
- If the user class is identical to the default, maintain the default.
|
|
30
|
+
- If the user class is different, **append** it to the default class (ensure a space separator).
|
|
31
|
+
|
|
32
|
+
```csharp
|
|
33
|
+
[HtmlAttributeName("class")]
|
|
34
|
+
public string? CssClass { get; set; }
|
|
35
|
+
|
|
36
|
+
protected void ProcessCssClass(TagHelperOutput output, string defaultClass)
|
|
37
|
+
{
|
|
38
|
+
if (string.IsNullOrEmpty(CssClass))
|
|
39
|
+
{
|
|
40
|
+
output.Attributes.SetAttribute("class", defaultClass);
|
|
41
|
+
}
|
|
42
|
+
else if (CssClass.Trim() == defaultClass)
|
|
43
|
+
{
|
|
44
|
+
output.Attributes.SetAttribute("class", defaultClass);
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
{
|
|
48
|
+
output.Attributes.SetAttribute("class", $"{defaultClass} {CssClass.Trim()}");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. **`AutoLoadAssets` (Asset Management)**:
|
|
54
|
+
Include a boolean property to control automatic resource loading, defaulting to `true`.
|
|
55
|
+
```csharp
|
|
56
|
+
/// <summary>
|
|
57
|
+
/// Whether to automatically load corresponding JS and CSS. Default is true.
|
|
58
|
+
/// </summary>
|
|
59
|
+
[HtmlAttributeName("auto-load-assets")]
|
|
60
|
+
public bool AutoLoadAssets { get; set; } = true;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2.2 Inheritance and Composition Standards
|
|
64
|
+
|
|
65
|
+
1. **Independent Tag Helpers**: Any standalone custom Tag Helper representing a single UI control (e.g., a specialized dropdown) MUST inherit from the corresponding built-in ASP.NET Core Tag Helper (e.g., `SelectTagHelper`, `InputTagHelper`, `RadioTagHelper`). This leverages framework-standard model binding and validation logic.
|
|
66
|
+
|
|
67
|
+
2. **Composite Tag Helpers**: For components aggregating multiple elements (e.g., a search form):
|
|
68
|
+
- **Internal Elements**: MUST be implemented using built-in C# UI Tag Helpers to ensure they benefit from standard ASP.NET Core features.
|
|
69
|
+
- **Outer Container (`div-class`)**: MUST allow users to pass a custom CSS class via a `div-class` attribute.
|
|
70
|
+
- **CSS Consistency**: The handling of `div-class` MUST strictly follow the rules defined in **Section 2.1.2 (CSS Class Handling)**, ensuring the default container class is preserved or appended to, rather than simply overwritten.
|
|
71
|
+
|
|
72
|
+
### 2.3 Mandatory Asset Loading Pattern
|
|
73
|
+
|
|
74
|
+
When `AutoLoadAssets` is `true`, the Tag Helper must register its resources using the following standard pattern:
|
|
75
|
+
|
|
76
|
+
- **Version Management**: Always use `IFileVersionProvider.AddFileVersionToPath` to ensure browser cache busting.
|
|
77
|
+
- **De-duplication & Injection**: Use `HttpContext.AddStyle` and `HttpContext.AddScript` (see Section 3 for implementation) to ensure assets are only rendered once per page.
|
|
78
|
+
|
|
79
|
+
```csharp
|
|
80
|
+
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
|
81
|
+
{
|
|
82
|
+
// ... UI Generation Logic ...
|
|
83
|
+
|
|
84
|
+
if (AutoLoadAssets)
|
|
85
|
+
{
|
|
86
|
+
var httpContext = ViewContext.HttpContext;
|
|
87
|
+
var requestPathBase = httpContext.Request.PathBase;
|
|
88
|
+
|
|
89
|
+
// 1. Resolve Path with Version
|
|
90
|
+
string cssPath = "/css/components/my-component.css";
|
|
91
|
+
string versionedCss = _fileVersionProvider.AddFileVersionToPath(requestPathBase, cssPath);
|
|
92
|
+
|
|
93
|
+
string jsPath = "/js/components/my-component.js";
|
|
94
|
+
string versionedJs = _fileVersionProvider.AddFileVersionToPath(requestPathBase, jsPath);
|
|
95
|
+
|
|
96
|
+
// 2. Register via Context Extensions (renders in designated Layout blocks)
|
|
97
|
+
httpContext.AddStyle($"<link rel=\"stylesheet\" href=\"{versionedCss}\" />", "MyComponentKey");
|
|
98
|
+
httpContext.AddScript($"<script src=\"{versionedJs}\"></script>", "MyComponentKey");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
## 3 Example
|
|
106
|
+
|
|
107
|
+
### Extending a Dropdown
|
|
108
|
+
|
|
15
109
|
```csharp
|
|
16
110
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
17
111
|
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
|
@@ -176,19 +270,22 @@ namespace [YourNamespace].TagHelpers
|
|
|
176
270
|
}
|
|
177
271
|
}
|
|
178
272
|
}
|
|
273
|
+
```
|
|
179
274
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
## 2. Composite Complex UI Components and Model Binding
|
|
275
|
+
### Composite Complex UI Components and Model Binding
|
|
183
276
|
|
|
184
277
|
For complex UIs containing multiple elements (like a complete search form), you should use `TagBuilder` to compose the DOM structure. Using `ModelExpression` allows you to accurately bind Tag Helper attributes to ViewModel properties, retrieve the exact field name (`.Name`) for the frontend, and integrate backend permission validation logic.
|
|
185
278
|
|
|
186
|
-
|
|
187
|
-
|
|
279
|
+
``` csharp
|
|
280
|
+
|
|
188
281
|
/// <summary>
|
|
189
282
|
/// Composite AI Report Search TagHelper.
|
|
190
283
|
/// A UI component integrating date range, business unit, submission unit, inspection report menu, and search button.
|
|
191
284
|
/// </summary>
|
|
285
|
+
/// <summary>
|
|
286
|
+
/// 組合式 AI 報告搜尋 TagHelper
|
|
287
|
+
/// 整合了日期範圍、檢驗單位、送檢單位、檢驗報告選單與查詢按鈕的 UI 組件
|
|
288
|
+
/// </summary>
|
|
192
289
|
[HtmlTargetElement("combo-ai-report-search")]
|
|
193
290
|
public class ComboAIReportSearchTagHelper : TagHelper
|
|
194
291
|
{
|
|
@@ -210,66 +307,72 @@ For complex UIs containing multiple elements (like a complete search form), you
|
|
|
210
307
|
}
|
|
211
308
|
|
|
212
309
|
/// <summary>
|
|
213
|
-
///
|
|
214
|
-
///
|
|
310
|
+
/// 是否自動載入對應的 JS 與 CSS,預設為 true
|
|
311
|
+
/// 若設定為 true,TagHelper 會自動在組件後方插入相關資源標籤,並確保單次請求僅載入一次
|
|
215
312
|
/// </summary>
|
|
216
313
|
[HtmlAttributeName("auto-load-assets")]
|
|
217
314
|
public bool AutoLoadAssets { get; set; } = true;
|
|
218
315
|
|
|
219
|
-
#region Model Binding Attributes (
|
|
316
|
+
#region Model Binding Attributes (支援自定義 ID 與 Name)
|
|
220
317
|
/// <summary>
|
|
221
|
-
///
|
|
318
|
+
/// 繫結開始日期的模型表達式
|
|
222
319
|
/// </summary>
|
|
223
320
|
[HtmlAttributeName("asp-for-sdate")]
|
|
224
321
|
public ModelExpression ForSDate { get; set; }
|
|
225
322
|
|
|
226
323
|
/// <summary>
|
|
227
|
-
///
|
|
324
|
+
/// 繫結結束日期的模型表達式
|
|
228
325
|
/// </summary>
|
|
229
326
|
[HtmlAttributeName("asp-for-edate")]
|
|
230
327
|
public ModelExpression ForEDate { get; set; }
|
|
231
328
|
|
|
232
329
|
/// <summary>
|
|
233
|
-
///
|
|
330
|
+
/// 繫結檢驗單位的模型表達式
|
|
234
331
|
/// </summary>
|
|
235
332
|
[HtmlAttributeName("asp-for-bu")]
|
|
236
333
|
public ModelExpression ForBusinessUnit { get; set; }
|
|
237
334
|
|
|
238
335
|
/// <summary>
|
|
239
|
-
///
|
|
336
|
+
/// 繫結送檢單位的模型表達式
|
|
240
337
|
/// </summary>
|
|
241
338
|
[HtmlAttributeName("asp-for-hisno")]
|
|
242
339
|
public ModelExpression ForHISNo { get; set; }
|
|
243
340
|
|
|
244
341
|
/// <summary>
|
|
245
|
-
///
|
|
342
|
+
/// 繫結檢驗報告的模型表達式
|
|
246
343
|
/// </summary>
|
|
247
344
|
[HtmlAttributeName("asp-for-report")]
|
|
248
345
|
public ModelExpression ForAIReport { get; set; }
|
|
249
346
|
#endregion
|
|
250
347
|
|
|
251
|
-
#region Class Attributes (
|
|
348
|
+
#region Class Attributes (支援 CSS 客製化)
|
|
252
349
|
[HtmlAttributeName("sdate-class")]
|
|
253
|
-
public string SDateClass { get; set; } = "form-control
|
|
350
|
+
public string SDateClass { get; set; } = "sdate-li77"; // form-control 要預設有
|
|
254
351
|
|
|
255
352
|
[HtmlAttributeName("edate-class")]
|
|
256
|
-
public string EDateClass { get; set; } = "form-control
|
|
353
|
+
public string EDateClass { get; set; } = "edate-li77"; // form-control 要預設有
|
|
257
354
|
|
|
258
355
|
[HtmlAttributeName("bu-class")]
|
|
259
|
-
public string BusinessUnitClass { get; set; } = "form-control
|
|
356
|
+
public string BusinessUnitClass { get; set; } = "bu-li77"; // form-control 要預設有
|
|
260
357
|
|
|
261
358
|
[HtmlAttributeName("hisno-class")]
|
|
262
|
-
public string HISNoClass { get; set; } = "form-control
|
|
359
|
+
public string HISNoClass { get; set; } = "hisno-li77"; // form-control 要預設有
|
|
263
360
|
|
|
264
361
|
[HtmlAttributeName("report-class")]
|
|
265
|
-
public string AIReportClass { get; set; } = "form-control
|
|
362
|
+
public string AIReportClass { get; set; } = "ai-report-li77"; // form-control 要預設有
|
|
266
363
|
|
|
267
364
|
[HtmlAttributeName("btn-class")]
|
|
268
|
-
public string BtnClass { get; set; } = "btn btn-primary
|
|
365
|
+
public string BtnClass { get; set; } = "ai-btn-search-li77"; // btn btn-primary 要預設有
|
|
366
|
+
|
|
367
|
+
/// <summary>
|
|
368
|
+
/// 外層容器的 CSS Class。預設為 ai-report-search-container-li77
|
|
369
|
+
/// </summary>
|
|
370
|
+
[HtmlAttributeName("div-class")]
|
|
371
|
+
public string ContainerClass { get; set; } = "ai-report-search-container-li77";
|
|
269
372
|
#endregion
|
|
270
373
|
|
|
271
374
|
/// <summary>
|
|
272
|
-
///
|
|
375
|
+
/// 取得目前的 ViewContext 以存取 HttpContext 等資訊
|
|
273
376
|
/// </summary>
|
|
274
377
|
[HtmlAttributeNotBound]
|
|
275
378
|
[ViewContext]
|
|
@@ -281,89 +384,94 @@ For complex UIs containing multiple elements (like a complete search form), you
|
|
|
281
384
|
var currentDT = _commonService.GetCurrentDateTime();
|
|
282
385
|
var isSuperAdmin = currentUser.IsSuperAdminRole;
|
|
283
386
|
|
|
284
|
-
//
|
|
387
|
+
// 設定外層容器
|
|
285
388
|
output.TagName = "div";
|
|
286
389
|
output.TagMode = TagMode.StartTagAndEndTag;
|
|
287
|
-
output.Attributes.SetAttribute("class", "ai-report-search-container");
|
|
288
390
|
|
|
289
|
-
//
|
|
290
|
-
string
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
391
|
+
// 處理自訂容器 Class,如果使用者自訂了,則把自訂的加在前面,並且保留預設的以便 JS 定位
|
|
392
|
+
string finalContainerClass = ContainerClass == "ai-report-search-container-li77"
|
|
393
|
+
? "ai-report-search-container-li77"
|
|
394
|
+
: $"{ContainerClass} ai-report-search-container-li77";
|
|
395
|
+
|
|
396
|
+
output.Attributes.SetAttribute("class", finalContainerClass);
|
|
397
|
+
|
|
398
|
+
// 產生日選單與按鈕的 ID (一律使用 TagBuilder.CreateSanitizedId 產生)
|
|
399
|
+
string sDateId = TagBuilder.CreateSanitizedId(ForSDate?.Name ?? "Search_SDate", "_");
|
|
400
|
+
string eDateId = TagBuilder.CreateSanitizedId(ForEDate?.Name ?? "Search_EDate", "_");
|
|
401
|
+
string buId = TagBuilder.CreateSanitizedId(ForBusinessUnit?.Name ?? "Search_BusinessUnit", "_");
|
|
402
|
+
string hisNoId = TagBuilder.CreateSanitizedId(ForHISNo?.Name ?? "Search_HISNo", "_");
|
|
403
|
+
string reportId = TagBuilder.CreateSanitizedId(ForAIReport?.Name ?? "Search_AIReport", "_");
|
|
404
|
+
|
|
405
|
+
// 處理內層元素的 CSS Class,確保基底 class (form-control / btn btn-primary) 加上自定義或預設的 class
|
|
406
|
+
string finalSDateClass = SDateClass == "sdate-li77" ? "form-control sdate-li77" : $"form-control {SDateClass} sdate-li77";
|
|
407
|
+
string finalEDateClass = EDateClass == "edate-li77" ? "form-control edate-li77" : $"form-control {EDateClass} edate-li77";
|
|
408
|
+
string finalBUClass = BusinessUnitClass == "bu-li77" ? "form-control bu-li77" : $"form-control {BusinessUnitClass} bu-li77";
|
|
409
|
+
string finalHISNoClass = HISNoClass == "hisno-li77" ? "form-control hisno-li77" : $"form-control {HISNoClass} hisno-li77";
|
|
410
|
+
string finalReportClass = AIReportClass == "ai-report-li77" ? "form-control ai-report-li77" : $"form-control {AIReportClass} ai-report-li77";
|
|
411
|
+
string finalBtnClass = BtnClass == "ai-btn-search-li77" ? "btn btn-primary ai-btn-search-li77" : $"btn btn-primary {BtnClass} ai-btn-search-li77";
|
|
296
412
|
|
|
297
413
|
var inputGroup = new TagBuilder("div");
|
|
298
414
|
inputGroup.AddCssClass("input-group");
|
|
299
415
|
|
|
300
|
-
// 1.
|
|
301
|
-
inputGroup.InnerHtml.AppendHtml(CreateSpan("
|
|
302
|
-
inputGroup.InnerHtml.AppendHtml(
|
|
416
|
+
// 1. 開始日期
|
|
417
|
+
inputGroup.InnerHtml.AppendHtml(CreateSpan("日期"));
|
|
418
|
+
inputGroup.InnerHtml.AppendHtml(await CreateUIAsync(ForSDate, "date", finalSDateClass, DateTime.Now.ToString("yyyy-MM-dd"), "開始日期", sDateId));
|
|
303
419
|
|
|
304
|
-
// 2.
|
|
305
|
-
inputGroup.InnerHtml.AppendHtml(CreateSpan("
|
|
306
|
-
inputGroup.InnerHtml.AppendHtml(
|
|
420
|
+
// 2. 結束日期
|
|
421
|
+
inputGroup.InnerHtml.AppendHtml(CreateSpan("至"));
|
|
422
|
+
inputGroup.InnerHtml.AppendHtml(await CreateUIAsync(ForEDate, "date", finalEDateClass, DateTime.Now.ToString("yyyy-MM-dd"), "結束日期", eDateId));
|
|
307
423
|
|
|
308
|
-
// 3.
|
|
424
|
+
// 3. 檢驗單位 (僅超級管理員可見)
|
|
309
425
|
if (isSuperAdmin)
|
|
310
426
|
{
|
|
311
|
-
inputGroup.InnerHtml.AppendHtml(CreateSpan("
|
|
312
|
-
inputGroup.InnerHtml.AppendHtml(
|
|
427
|
+
inputGroup.InnerHtml.AppendHtml(CreateSpan("檢驗單位"));
|
|
428
|
+
inputGroup.InnerHtml.AppendHtml(await CreateUIAsync(ForBusinessUnit, null, finalBUClass, null, null, buId));
|
|
313
429
|
}
|
|
314
430
|
|
|
315
|
-
// 4.
|
|
316
|
-
inputGroup.InnerHtml.AppendHtml(CreateSpan("
|
|
317
|
-
|
|
431
|
+
// 4. 送檢單位 (依權限自動過濾或初始化)
|
|
432
|
+
inputGroup.InnerHtml.AppendHtml(CreateSpan("送檢單位"));
|
|
433
|
+
|
|
434
|
+
List<SelectListItem> hisNoOptions = new List<SelectListItem>();
|
|
318
435
|
if (!isSuperAdmin)
|
|
319
436
|
{
|
|
320
|
-
//
|
|
437
|
+
// 非超級管理員:預先從資料庫抓取授權的組織清單
|
|
438
|
+
hisNoOptions.Add(new SelectListItem { Text = "", Value = "" });
|
|
321
439
|
var options = await GetAllowListOptionsAsync(currentUser.UserID, currentDT);
|
|
322
|
-
|
|
323
|
-
foreach (var opt in options)
|
|
324
|
-
{
|
|
325
|
-
var optionTag = new TagBuilder("option");
|
|
326
|
-
optionTag.Attributes.Add("value", opt.Value);
|
|
327
|
-
optionTag.InnerHtml.Append(opt.Text);
|
|
328
|
-
hisNoSelect.InnerHtml.AppendHtml(optionTag);
|
|
329
|
-
}
|
|
440
|
+
hisNoOptions.AddRange(options);
|
|
330
441
|
}
|
|
331
|
-
inputGroup.InnerHtml.AppendHtml(
|
|
442
|
+
inputGroup.InnerHtml.AppendHtml(await CreateUIAsync(ForHISNo, null, finalHISNoClass, null, null, hisNoId, hisNoOptions));
|
|
332
443
|
|
|
333
|
-
// 5.
|
|
334
|
-
inputGroup.InnerHtml.AppendHtml(CreateSpan("
|
|
335
|
-
var
|
|
336
|
-
|
|
337
|
-
inputGroup.InnerHtml.AppendHtml(reportSelect);
|
|
444
|
+
// 5. 檢驗報告 (預設為空,由 JS 透過 API 非同步填充)
|
|
445
|
+
inputGroup.InnerHtml.AppendHtml(CreateSpan("檢驗報告"));
|
|
446
|
+
var reportOptions = new List<SelectListItem> { new SelectListItem { Text = "", Value = "" } };
|
|
447
|
+
inputGroup.InnerHtml.AppendHtml(await CreateUIAsync(ForAIReport, null, finalReportClass, null, null, reportId, reportOptions));
|
|
338
448
|
|
|
339
|
-
// 6.
|
|
449
|
+
// 6. 查詢按鈕
|
|
340
450
|
var btn = new TagBuilder("button");
|
|
341
451
|
btn.Attributes.Add("type", "button");
|
|
342
|
-
btn.
|
|
343
|
-
btn.
|
|
344
|
-
btn.InnerHtml.AppendHtml("<i class='fa fa-search'></i> Search");
|
|
452
|
+
btn.AddCssClass(finalBtnClass);
|
|
453
|
+
btn.InnerHtml.AppendHtml("<i class='fa fa-search'></i> 查詢");
|
|
345
454
|
inputGroup.InnerHtml.AppendHtml(btn);
|
|
346
455
|
|
|
347
456
|
output.Content.SetHtmlContent(inputGroup);
|
|
348
457
|
|
|
349
|
-
//
|
|
458
|
+
// 自動載入資產
|
|
350
459
|
if (AutoLoadAssets)
|
|
351
460
|
{
|
|
352
461
|
var httpContext = ViewContext.HttpContext;
|
|
353
462
|
|
|
354
|
-
|
|
355
|
-
string cssPath = "[your-css-path]";
|
|
463
|
+
string cssPath = "/css/AIAnalysis/ComboAIReportSearch.css";
|
|
356
464
|
string vCssPath = _fileVersionProvider.AddFileVersionToPath(httpContext.Request.PathBase, cssPath);
|
|
357
465
|
httpContext.AddStyle($"<link rel=\"stylesheet\" href=\"{vCssPath}\" />", "ComboAIReportSearch");
|
|
358
466
|
|
|
359
|
-
|
|
360
|
-
string jsPath = "[your-js-path]";
|
|
467
|
+
string jsPath = "/js/AIAnalysis/ComboAIReportSearch.js";
|
|
361
468
|
string vJsPath = _fileVersionProvider.AddFileVersionToPath(httpContext.Request.PathBase, jsPath);
|
|
362
469
|
httpContext.AddScript($"<script src=\"{vJsPath}\"></script>", "ComboAIReportSearch");
|
|
363
470
|
}
|
|
364
471
|
}
|
|
365
472
|
|
|
366
|
-
#region Helper Methods
|
|
473
|
+
#region Helper Methods
|
|
474
|
+
|
|
367
475
|
private TagBuilder CreateSpan(string text)
|
|
368
476
|
{
|
|
369
477
|
var span = new TagBuilder("span");
|
|
@@ -372,30 +480,105 @@ For complex UIs containing multiple elements (like a complete search form), you
|
|
|
372
480
|
return span;
|
|
373
481
|
}
|
|
374
482
|
|
|
375
|
-
|
|
483
|
+
/// <summary>
|
|
484
|
+
/// 通用 UI 產生方法,根據是否有 ModelExpression 選擇產生方式
|
|
485
|
+
/// </summary>
|
|
486
|
+
private async Task<IHtmlContent> CreateUIAsync(ModelExpression @for, string type, string className, string value, string title, string sanitizedId, IEnumerable<SelectListItem> items = null)
|
|
487
|
+
{
|
|
488
|
+
// 如果有 ModelExpression,使用原生 TagHelper 物件
|
|
489
|
+
if (@for != null)
|
|
490
|
+
{
|
|
491
|
+
if (string.IsNullOrEmpty(type)) // Select
|
|
492
|
+
{
|
|
493
|
+
return await CreateSelectTagHelperAsync(@for, className, sanitizedId, items);
|
|
494
|
+
}
|
|
495
|
+
else // Input
|
|
496
|
+
{
|
|
497
|
+
return await CreateInputTagHelperAsync(@for, type, className, value, title, sanitizedId);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// 如果沒有 ModelExpression,使用 IHtmlGenerator 直接產生 (模擬 TagHelper 行為)
|
|
502
|
+
if (string.IsNullOrEmpty(type)) // Select
|
|
503
|
+
{
|
|
504
|
+
var select = _generator.GenerateSelect(
|
|
505
|
+
ViewContext,
|
|
506
|
+
null,
|
|
507
|
+
sanitizedId,
|
|
508
|
+
sanitizedId,
|
|
509
|
+
items ?? new List<SelectListItem>(),
|
|
510
|
+
false,
|
|
511
|
+
new { @class = className, id = sanitizedId });
|
|
512
|
+
return select;
|
|
513
|
+
}
|
|
514
|
+
else // Input
|
|
515
|
+
{
|
|
516
|
+
var input = _generator.GenerateTextBox(
|
|
517
|
+
ViewContext,
|
|
518
|
+
null,
|
|
519
|
+
sanitizedId,
|
|
520
|
+
value,
|
|
521
|
+
null,
|
|
522
|
+
new { @class = className, id = sanitizedId, type = type, title = title });
|
|
523
|
+
return input;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/// <summary>
|
|
528
|
+
/// 使用 InputTagHelper 產生輸入框
|
|
529
|
+
/// </summary>
|
|
530
|
+
private async Task<IHtmlContent> CreateInputTagHelperAsync(ModelExpression @for, string type, string className, string value, string title, string sanitizedId)
|
|
376
531
|
{
|
|
377
|
-
var
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
532
|
+
var inputTagHelper = new InputTagHelper(_generator)
|
|
533
|
+
{
|
|
534
|
+
For = @for,
|
|
535
|
+
InputTypeName = type,
|
|
536
|
+
ViewContext = this.ViewContext
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
var output = new TagHelperOutput(
|
|
540
|
+
"input",
|
|
541
|
+
new TagHelperAttributeList(),
|
|
542
|
+
(useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
output.Attributes.SetAttribute("id", sanitizedId);
|
|
546
|
+
if (!string.IsNullOrEmpty(className)) output.Attributes.SetAttribute("class", className);
|
|
547
|
+
if (!string.IsNullOrEmpty(value)) output.Attributes.SetAttribute("value", value);
|
|
548
|
+
if (!string.IsNullOrEmpty(title)) output.Attributes.SetAttribute("title", title);
|
|
549
|
+
|
|
550
|
+
await inputTagHelper.ProcessAsync(new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), Guid.NewGuid().ToString()), output);
|
|
551
|
+
return output;
|
|
385
552
|
}
|
|
386
553
|
|
|
387
|
-
|
|
554
|
+
/// <summary>
|
|
555
|
+
/// 使用 SelectTagHelper 產生下拉選單
|
|
556
|
+
/// </summary>
|
|
557
|
+
private async Task<IHtmlContent> CreateSelectTagHelperAsync(ModelExpression @for, string className, string sanitizedId, IEnumerable<SelectListItem> items = null)
|
|
388
558
|
{
|
|
389
|
-
var
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
559
|
+
var selectTagHelper = new SelectTagHelper(_generator)
|
|
560
|
+
{
|
|
561
|
+
For = @for,
|
|
562
|
+
Items = items ?? new List<SelectListItem>(),
|
|
563
|
+
ViewContext = this.ViewContext
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
var output = new TagHelperOutput(
|
|
567
|
+
"select",
|
|
568
|
+
new TagHelperAttributeList(),
|
|
569
|
+
(useCachedResult, encoder) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent())
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
output.Attributes.SetAttribute("id", sanitizedId);
|
|
573
|
+
if (!string.IsNullOrEmpty(className)) output.Attributes.SetAttribute("class", className);
|
|
574
|
+
|
|
575
|
+
await selectTagHelper.ProcessAsync(new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), Guid.NewGuid().ToString()), output);
|
|
576
|
+
return output;
|
|
394
577
|
}
|
|
395
578
|
#endregion
|
|
396
579
|
|
|
397
580
|
/// <summary>
|
|
398
|
-
///
|
|
581
|
+
/// 獲取當前使用者被授權的所有送檢機構列表
|
|
399
582
|
/// </summary>
|
|
400
583
|
private async Task<List<SelectListItem>> GetAllowListOptionsAsync(string userId, DateTime currentDT)
|
|
401
584
|
{
|
|
@@ -403,18 +586,18 @@ For complex UIs containing multiple elements (like a complete search form), you
|
|
|
403
586
|
return authOrgs.Select(x => new SelectListItem
|
|
404
587
|
{
|
|
405
588
|
Value = x.HisNo,
|
|
406
|
-
Text = $"
|
|
589
|
+
Text = $"【{x.HisNo}】 {x.OrgName}"
|
|
407
590
|
}).ToList();
|
|
408
591
|
}
|
|
409
592
|
}
|
|
410
593
|
|
|
411
|
-
|
|
594
|
+
```
|
|
595
|
+
|
|
412
596
|
|
|
413
|
-
|
|
597
|
+
### Automatic Frontend Asset Loading and Deduplication (Asset Management)
|
|
414
598
|
|
|
415
599
|
When a Tag Helper depends on specific JavaScript or CSS, developers must ensure that these resources are not loaded multiple times if the tag is used repeatedly on the same page. It is recommended to implement an asset collector pattern by extending `HttpContext.Items`.
|
|
416
600
|
|
|
417
|
-
### Example: Asset Deduplication Extension Methods
|
|
418
601
|
|
|
419
602
|
```csharp
|
|
420
603
|
public static partial class HtmlExtension
|
|
@@ -492,83 +675,4 @@ public static partial class HtmlExtension
|
|
|
492
675
|
|
|
493
676
|
---
|
|
494
677
|
|
|
495
|
-
## 4. Mandatory Custom Tag Helper Properties & Asset Management
|
|
496
|
-
|
|
497
|
-
To ensure consistency, performance, and maintainability across all custom UI components, every custom Tag Helper MUST implement the following properties and logic.
|
|
498
|
-
|
|
499
|
-
### 4.1 Mandatory Properties
|
|
500
|
-
|
|
501
|
-
1. **`asp-for` (Model Binding)**:
|
|
502
|
-
Support strong typing by including a `ModelExpression` property. This is crucial for retrieving model metadata (Name, Id, Value, Validation) and generating safe HTML identifiers.
|
|
503
|
-
```csharp
|
|
504
|
-
[HtmlAttributeName("asp-for")]
|
|
505
|
-
public ModelExpression For { get; set; } = null!;
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
2. **CSS Class Handling (UI Class)**:
|
|
509
|
-
Tag Helpers must provide a default CSS class. If the user provides a custom class in the HTML tag:
|
|
510
|
-
- If the user class is identical to the default, maintain the default.
|
|
511
|
-
- If the user class is different, **append** it to the default class (ensure a space separator).
|
|
512
|
-
|
|
513
|
-
```csharp
|
|
514
|
-
[HtmlAttributeName("class")]
|
|
515
|
-
public string? CssClass { get; set; }
|
|
516
678
|
|
|
517
|
-
protected void ProcessCssClass(TagHelperOutput output, string defaultClass)
|
|
518
|
-
{
|
|
519
|
-
if (string.IsNullOrEmpty(CssClass))
|
|
520
|
-
{
|
|
521
|
-
output.Attributes.SetAttribute("class", defaultClass);
|
|
522
|
-
}
|
|
523
|
-
else if (CssClass.Trim() == defaultClass)
|
|
524
|
-
{
|
|
525
|
-
output.Attributes.SetAttribute("class", defaultClass);
|
|
526
|
-
}
|
|
527
|
-
else
|
|
528
|
-
{
|
|
529
|
-
output.Attributes.SetAttribute("class", $"{defaultClass} {CssClass.Trim()}");
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
3. **`AutoLoadAssets` (Asset Management)**:
|
|
535
|
-
Include a boolean property to control automatic resource loading, defaulting to `true`.
|
|
536
|
-
```csharp
|
|
537
|
-
/// <summary>
|
|
538
|
-
/// Whether to automatically load corresponding JS and CSS. Default is true.
|
|
539
|
-
/// </summary>
|
|
540
|
-
[HtmlAttributeName("auto-load-assets")]
|
|
541
|
-
public bool AutoLoadAssets { get; set; } = true;
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### 4.2 Mandatory Asset Loading Pattern
|
|
545
|
-
|
|
546
|
-
When `AutoLoadAssets` is `true`, the Tag Helper must register its resources using the following standard pattern:
|
|
547
|
-
|
|
548
|
-
- **Version Management**: Always use `IFileVersionProvider.AddFileVersionToPath` to ensure browser cache busting.
|
|
549
|
-
- **De-duplication & Injection**: Use `HttpContext.AddStyle` and `HttpContext.AddScript` (see Section 3 for implementation) to ensure assets are only rendered once per page.
|
|
550
|
-
|
|
551
|
-
```csharp
|
|
552
|
-
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
|
553
|
-
{
|
|
554
|
-
// ... UI Generation Logic ...
|
|
555
|
-
|
|
556
|
-
if (AutoLoadAssets)
|
|
557
|
-
{
|
|
558
|
-
var httpContext = ViewContext.HttpContext;
|
|
559
|
-
var requestPathBase = httpContext.Request.PathBase;
|
|
560
|
-
|
|
561
|
-
// 1. Resolve Path with Version
|
|
562
|
-
string cssPath = "/css/components/my-component.css";
|
|
563
|
-
string versionedCss = _fileVersionProvider.AddFileVersionToPath(requestPathBase, cssPath);
|
|
564
|
-
|
|
565
|
-
string jsPath = "/js/components/my-component.js";
|
|
566
|
-
string versionedJs = _fileVersionProvider.AddFileVersionToPath(requestPathBase, jsPath);
|
|
567
|
-
|
|
568
|
-
// 2. Register via Context Extensions (renders in designated Layout blocks)
|
|
569
|
-
httpContext.AddStyle($"<link rel=\"stylesheet\" href=\"{versionedCss}\" />", "MyComponentKey");
|
|
570
|
-
httpContext.AddScript($"<script src=\"{versionedJs}\"></script>", "MyComponentKey");
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
```
|
|
574
|
-
```
|