rvlib-shipkit 1.9.0 → 2.0.0
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/CHANGELOG.md +34 -0
- package/package.json +1 -1
- package/src/check.ts +5 -2
- package/src/checks/SK001-package-json-shape.check.ts +47 -12
- package/src/checks/SK013-one-way-in-policy.check.ts +8 -7
- package/src/checks/SK017-vscode-hub-task.check.ts +109 -106
- package/src/checks/SK019-tests-run-in-units.check.ts +2 -1
- package/src/checks/SK027-agent-wiki-links.check.ts +5 -2
- package/src/checks/SK029-explicit-check-files.check.ts +25 -8
- package/src/checks/SK034-commit-gates-armed.check.ts +90 -0
- package/src/cli/cmd_Ci.ts +3 -3
- package/src/cli/cmd_Commands.ts +21 -5
- package/src/cli/cmd_Hooks.ts +6 -2
- package/src/cli/cmd_Init.ts +1 -1
- package/src/cli/cmd_Serve.ts +1 -2
- package/src/cli/cmd_Start.ts +1 -1
- package/src/cli.ts +2 -4
- package/src/core/budget.ts +6 -8
- package/src/core/bun-builtins.ts +36 -0
- package/src/core/bun-tool-cli.ts +23 -0
- package/src/core/canonical-scripts.ts +6 -4
- package/src/core/check-ctx.ts +104 -10
- package/src/core/check-file-names.ts +2 -3
- package/src/core/check-pass.ts +17 -1
- package/src/core/check-runner.ts +19 -6
- package/src/core/config-rewrite.ts +27 -6
- package/src/core/discover-checks.ts +30 -10
- package/src/core/floor-resolve.ts +16 -7
- package/src/core/job-run.ts +2 -2
- package/src/core/layout.ts +4 -1
- package/src/core/legacy-shapes.ts +40 -0
- package/src/core/one-lib.ts +1 -1
- package/src/core/one-way-in-policy.ts +106 -13
- package/src/core/pre-commit-discovery.ts +106 -0
- package/src/core/retired-checks.ts +2 -0
- package/src/core/run-ci.ts +16 -10
- package/src/core/run-fix.ts +9 -0
- package/src/core/run-fixers.ts +91 -35
- package/src/core/runs-matrix.ts +7 -0
- package/src/core/slots.ts +1 -1
- package/src/core/vendored-read.ts +39 -0
- package/src/core/vendored.ts +82 -0
- package/src/core/waivers.ts +125 -0
- package/src/core/when-block.ts +25 -185
- package/src/core/when.ts +30 -13
- package/src/core/wire.ts +17 -1
- package/src/git-bootstrap.ts +1 -1
- package/src/home/attach.ts +3 -5
- package/src/home/insights.ts +6 -1
- package/src/home/pm-config.ts +2 -2
- package/src/home/run-daemon.ts +2 -2
- package/src/home/run-unit-action.ts +2 -2
- package/src/hooks/commit-message.ts +2 -14
- package/src/hooks/hook-gates.ts +38 -0
- package/src/hooks/leak-guard.ts +8 -3
- package/src/hooks/one-way-in-guard.ts +8 -2
- package/src/hooks/root-identity.ts +9 -6
- package/src/hooks/run-hooks.ts +72 -41
- package/src/hooks/staged.ts +49 -0
- package/src/hooks/tree-shrink.ts +41 -0
- package/src/init-repo.ts +0 -42
- package/src/ledger/check-table.ts +1 -1
- package/src/ledger/gate-checks.ts +45 -9
- package/src/ledger/ledger.ts +6 -1
- package/src/ledger/repo-view.ts +14 -4
- package/src/ledger/toolchain-identity.ts +4 -2
- package/src/mv/mv-md.ts +6 -1
- package/src/problems/extract.ts +27 -5
- package/src/problems/problem-line.ts +2 -3
- package/src/release/run-release.ts +13 -8
- package/src/repo-config.ts +44 -27
- package/src/scaffold/files/bunfig.toml +11 -0
- package/src/scaffold/files/vscode-settings.json +1 -2
- package/src/scaffold/jsonc-spans.ts +16 -0
- package/src/scaffold/project-scaffold.ts +43 -77
- package/src/serve/hub-app/check-view.tsx +4 -4
- package/src/serve/hub-app/check-words.ts +1 -0
- package/src/serve/hub-app/checks-svc.ts +1 -2
- package/src/serve/hub-app/home.html +0 -1
- package/src/serve/hub-app/hub-types.ts +0 -2
- package/src/serve/hub-app/index.html +0 -1
- package/src/serve/hub-app/project-list.css +1 -2
- package/src/serve/hub-app/tree-build.ts +3 -2
- package/src/serve/hub-app/tree-svc.ts +2 -2
- package/src/serve/hub-port.ts +3 -3
- package/src/serve/hub-runtime.ts +0 -3
- package/src/serve/serve-hub.ts +1 -16
- package/src/serve/tracked-watch.ts +1 -1
- package/src/start-repo.ts +15 -3
- package/src/suite/judge-suites.ts +29 -28
- package/src/suite/select.ts +2 -0
- package/src/suite/suite-cli.ts +6 -3
- package/src/suite/suite-cpu-preload.ts +7 -11
- package/src/suite/test-units.ts +7 -4
- package/src/ts/config.ts +12 -5
- package/src/ts/imports.ts +3 -1
- package/src/tui/build-steps-panel.ts +1 -1
- package/src/wiki/scan-md.ts +16 -9
- package/src/checks/SK009-commit-ai-tells.check.ts +0 -131
- package/src/checks/SK014-prose-ste.check.ts +0 -78
- package/src/cli/cmd_Prose.ts +0 -74
- package/src/prose/prose-view.ts +0 -90
- package/src/prose/run-prose.ts +0 -187
- package/src/prose/ste.ts +0 -552
- package/src/scaffold/files/vscode-tasks.json +0 -24
- package/src/serve/hub-app/prose-hits-st.ts +0 -125
- package/src/serve/hub-app/prose-hits-view.tsx +0 -176
- package/src/serve/hub-app/prose-hits.css +0 -21
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// The SK014 page's own state: the hits the server grouped, the filters over
|
|
2
|
-
// them, and the word list form. Component scoped (it lives while the page is
|
|
3
|
-
// open), and every write goes through the ONE config writer the run when
|
|
4
|
-
// switch uses (checks-svc.ts writeGate), so a word list edit is refused for
|
|
5
|
-
// the same reasons a switch is.
|
|
6
|
-
import { makeAutoObservable, observableRef, runInAction } from 'mobx'
|
|
7
|
-
import type { ProseGroup, ProseView } from '../../prose/prose-view.ts'
|
|
8
|
-
import type { RepoSt } from './repo-st.ts'
|
|
9
|
-
import { ownTrouble, reach } from './reach.ts'
|
|
10
|
-
|
|
11
|
-
export type TierFilter = 'all' | 'blocking' | 'soft'
|
|
12
|
-
export type ListKind = 'allow' | 'ban'
|
|
13
|
-
|
|
14
|
-
/** how many groups the page draws before a "show every word" button */
|
|
15
|
-
export const GROUP_CAP = 40
|
|
16
|
-
/** how many hits one open group draws before its own "show all" */
|
|
17
|
-
export const HIT_CAP = 60
|
|
18
|
-
|
|
19
|
-
export class ProseHitsSt {
|
|
20
|
-
view: ProseView | null = null
|
|
21
|
-
error = ''
|
|
22
|
-
loading = false
|
|
23
|
-
word = ''
|
|
24
|
-
file = ''
|
|
25
|
-
tier: TierFilter = 'all'
|
|
26
|
-
allGroups = false
|
|
27
|
-
/** group keys the reader opened */
|
|
28
|
-
open = new Set<string>()
|
|
29
|
-
/** group keys whose every hit is drawn */
|
|
30
|
-
full = new Set<string>()
|
|
31
|
-
form: { kind: ListKind; word: string; to: string; reason: string } = { kind: 'allow', word: '', to: '', reason: '' }
|
|
32
|
-
|
|
33
|
-
constructor(private st: RepoSt) {
|
|
34
|
-
// .ref: the view is swapped whole on every load, never edited in place
|
|
35
|
-
makeAutoObservable<this, 'st'>(this, { st: false, view: observableRef })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async load(): Promise<void> {
|
|
39
|
-
runInAction(() => { this.loading = true })
|
|
40
|
-
try {
|
|
41
|
-
const r = await reach(this.st.url('/api/prose.json'))
|
|
42
|
-
if (!r.ok) throw new Error(await r.text())
|
|
43
|
-
const v = (await r.json()) as ProseView
|
|
44
|
-
runInAction(() => { this.view = v; this.error = '' })
|
|
45
|
-
} catch (e) {
|
|
46
|
-
runInAction(() => { this.error = ownTrouble(e, this.st.home.machine, (m) => `the hits could not be read: ${m}`) })
|
|
47
|
-
}
|
|
48
|
-
runInAction(() => { this.loading = false })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
setWord(v: string): void { this.word = v }
|
|
52
|
-
setFile(v: string): void { this.file = v }
|
|
53
|
-
setTier(v: TierFilter): void { this.tier = v }
|
|
54
|
-
showAllGroups(): void { this.allGroups = true }
|
|
55
|
-
toggle(key: string): void {
|
|
56
|
-
if (this.open.has(key)) this.open.delete(key)
|
|
57
|
-
else this.open.add(key)
|
|
58
|
-
}
|
|
59
|
-
showAllHits(key: string): void { this.full.add(key) }
|
|
60
|
-
setForm<K extends keyof ProseHitsSt['form']>(k: K, v: ProseHitsSt['form'][K]): void {
|
|
61
|
-
this.form = { ...this.form, [k]: v }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** the groups under the filters, each recounted over the hits it keeps,
|
|
65
|
-
* so a file filter reorders the words by their noise IN that file */
|
|
66
|
-
get groups(): ProseGroup[] {
|
|
67
|
-
const v = this.view
|
|
68
|
-
if (v == null) return []
|
|
69
|
-
const w = this.word.trim().toLowerCase()
|
|
70
|
-
const f = this.file.trim().toLowerCase()
|
|
71
|
-
const out: ProseGroup[] = []
|
|
72
|
-
for (const g of v.groups) {
|
|
73
|
-
if (this.tier === 'blocking' && g.tier === 'soft') continue
|
|
74
|
-
if (this.tier === 'soft' && g.tier !== 'soft') continue
|
|
75
|
-
if (w !== '' && !(g.word ?? g.cls).includes(w) && !g.cls.includes(w)) continue
|
|
76
|
-
const hits = f === '' ? g.hits : g.hits.filter((h) => h.file.toLowerCase().includes(f))
|
|
77
|
-
if (hits.length === 0) continue
|
|
78
|
-
out.push(hits === g.hits ? g : { ...g, hits, count: hits.length })
|
|
79
|
-
}
|
|
80
|
-
return out.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key))
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
get shownCount(): number {
|
|
84
|
-
return this.groups.reduce((n, g) => n + g.count, 0)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** every file that carries a hit, for the file filter's suggestions */
|
|
88
|
-
get files(): string[] {
|
|
89
|
-
const s = new Set<string>()
|
|
90
|
-
for (const g of this.view?.groups ?? []) for (const h of g.hits) s.add(h.file)
|
|
91
|
-
return [...s].sort()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
get busy(): boolean { return this.st.checks.gateBusy }
|
|
95
|
-
|
|
96
|
-
/** after any write: the config moved, so the hits are re-read and the
|
|
97
|
-
* rule reruns, and its row recolours on the tree like any other run */
|
|
98
|
-
private async after(ok: boolean): Promise<void> {
|
|
99
|
-
if (!ok) return
|
|
100
|
-
await this.load()
|
|
101
|
-
void this.st.checks.run(['SK014'])
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async allow(word: string, reason = ''): Promise<void> {
|
|
105
|
-
const ok = await this.st.checks.writeGate({ words: { allow: [{ word, reason }] } }, `'${word}' is allowed in this repo — repo.config.ts is edited and waiting for your commit`)
|
|
106
|
-
await this.after(ok)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async submit(): Promise<void> {
|
|
110
|
-
const f = this.form
|
|
111
|
-
if (f.word.trim() === '') return
|
|
112
|
-
const entry = f.kind === 'allow' ? { allow: [{ word: f.word, reason: f.reason }] } : { ban: [{ word: f.word, to: f.to, reason: f.reason }] }
|
|
113
|
-
const said = f.kind === 'allow'
|
|
114
|
-
? `'${f.word.trim()}' is allowed in this repo — repo.config.ts is edited and waiting for your commit`
|
|
115
|
-
: `'${f.word.trim()}' is refused in this repo — repo.config.ts is edited and waiting for your commit`
|
|
116
|
-
const ok = await this.st.checks.writeGate({ words: entry }, said)
|
|
117
|
-
if (ok) runInAction(() => { this.form = { kind: this.form.kind, word: '', to: '', reason: '' } })
|
|
118
|
-
await this.after(ok)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async remove(word: string): Promise<void> {
|
|
122
|
-
const ok = await this.st.checks.writeGate({ words: { remove: [word] } }, `'${word}' is back to the house dictionary — repo.config.ts is edited and waiting for your commit`)
|
|
123
|
-
await this.after(ok)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
// THE SK014 PAGE's own sections: what the prose check found, grouped by word
|
|
2
|
-
// with the noisiest on top, each hit a file:line link into the editor and the
|
|
3
|
-
// sentence around it with the word marked; then the repo's own word lists,
|
|
4
|
-
// editable in place. The house dictionary stays the default, a list entry
|
|
5
|
-
// overrides it for this repo only.
|
|
6
|
-
import { observer } from 'mobx-react-lite'
|
|
7
|
-
import { useEffect, useMemo } from 'react'
|
|
8
|
-
import type { ProseGroup, ProseViewHit } from '../../prose/prose-view.ts'
|
|
9
|
-
import type { ProseWords } from '../../prose/ste.ts'
|
|
10
|
-
import { editorHref } from './check-words.ts'
|
|
11
|
-
import { Help } from './help.tsx'
|
|
12
|
-
import type { RepoSt } from './repo-st.ts'
|
|
13
|
-
import { GROUP_CAP, HIT_CAP, ProseHitsSt, type ListKind, type TierFilter } from './prose-hits-st.ts'
|
|
14
|
-
|
|
15
|
-
/** blocking tiers read red, the soft tier reads as a warning */
|
|
16
|
-
const TIER_WORD: Record<string, { word: string; tone: string }> = {
|
|
17
|
-
fix: { word: 'blocks, --fix rewrites it', tone: 'red' },
|
|
18
|
-
hard: { word: 'blocks', tone: 'red' },
|
|
19
|
-
soft: { word: 'soft', tone: 'warn' },
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const TIERS: { v: TierFilter; label: string }[] = [
|
|
23
|
-
{ v: 'all', label: 'every hit' },
|
|
24
|
-
{ v: 'blocking', label: 'what blocks' },
|
|
25
|
-
{ v: 'soft', label: 'soft only' },
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
/** the sentence around a hit, the hit itself marked */
|
|
29
|
-
const Excerpt = ({ h }: { h: ProseViewHit }) => (
|
|
30
|
-
<span className="pexc">
|
|
31
|
-
{h.excerpt.slice(0, h.mark[0])}
|
|
32
|
-
<mark>{h.excerpt.slice(h.mark[0], h.mark[1])}</mark>
|
|
33
|
-
{h.excerpt.slice(h.mark[1])}
|
|
34
|
-
</span>
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
const HitLine = ({ root, h, shape }: { root: string; h: ProseViewHit; shape: boolean }) => {
|
|
38
|
-
const href = editorHref(root, h.file)
|
|
39
|
-
const at = `${h.file}:${h.line}`
|
|
40
|
-
return (
|
|
41
|
-
<div className="phit">
|
|
42
|
-
{href == null ? <code className="cpath">{at}</code> : <a href={`${href}:${h.line}`} title="open it in the editor"><code className="cpath">{at}</code></a>}
|
|
43
|
-
<Excerpt h={h} />
|
|
44
|
-
{/* a shape's reason names its own numbers (31 words, 8 sentences) */}
|
|
45
|
-
{shape ? <span className="dim pwhy">{h.why}</span> : null}
|
|
46
|
-
</div>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const Group = observer(({ app, s, g }: { app: RepoSt; s: ProseHitsSt; g: ProseGroup }) => {
|
|
51
|
-
const open = s.open.has(g.key)
|
|
52
|
-
const t = TIER_WORD[g.tier] ?? { word: g.tier, tone: '' }
|
|
53
|
-
const shown = s.full.has(g.key) ? g.hits : g.hits.slice(0, HIT_CAP)
|
|
54
|
-
return (
|
|
55
|
-
<div className="pgroup">
|
|
56
|
-
<div className="phead">
|
|
57
|
-
<button className="ptoggle" onClick={() => s.toggle(g.key)} title={open ? 'hide the hits' : 'show the hits'}>{open ? '▾' : '▸'}</button>
|
|
58
|
-
<span className="pcount dnum">{g.count}</span>
|
|
59
|
-
<b className="pword">{g.word ?? g.cls}</b>
|
|
60
|
-
<span className="stchip">{g.cls}</span>
|
|
61
|
-
<span className={`tl ${t.tone}`}>{t.word}</span>
|
|
62
|
-
<span className="dim pwhy">{g.word == null ? null : g.hits[0]?.why}</span>
|
|
63
|
-
<span className="sp" />
|
|
64
|
-
{g.word == null
|
|
65
|
-
? null
|
|
66
|
-
: <button disabled={s.busy} title="accept this word in this repo: every hit on it goes away" onClick={() => void s.allow(g.word!)}>allow it here</button>}
|
|
67
|
-
</div>
|
|
68
|
-
{open
|
|
69
|
-
? <div className="phits">
|
|
70
|
-
{shown.map((h, n) => <HitLine key={`${h.file}:${h.line}:${n}`} root={app.root} h={h} shape={g.word == null} />)}
|
|
71
|
-
{shown.length < g.hits.length
|
|
72
|
-
? <button className="pmore" onClick={() => s.showAllHits(g.key)}>show the other {g.hits.length - shown.length}</button>
|
|
73
|
-
: null}
|
|
74
|
-
</div>
|
|
75
|
-
: null}
|
|
76
|
-
</div>
|
|
77
|
-
)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const Hits = observer(({ app, s }: { app: RepoSt; s: ProseHitsSt }) => {
|
|
81
|
-
const v = s.view
|
|
82
|
-
if (v == null) return <div className="dim">{s.error !== '' ? s.error : 'reading the hits…'}</div>
|
|
83
|
-
if (!v.scoped) return <div className="dim">this repo declares no prose scope, so the check reads no file: add `prose: {'{'} include: [...] {'}'}` to repo.config.ts</div>
|
|
84
|
-
const groups = s.groups
|
|
85
|
-
const drawn = s.allGroups ? groups : groups.slice(0, GROUP_CAP)
|
|
86
|
-
const blocking = v.byTier.fix + v.byTier.hard
|
|
87
|
-
return (
|
|
88
|
-
<>
|
|
89
|
-
<div className="wline">
|
|
90
|
-
{v.total} hit(s) in {v.files} file(s): <span className={blocking > 0 ? 'tl red' : 'dim'}>{blocking} block</span>, <span className="tl warn">{v.byTier.soft} soft</span>
|
|
91
|
-
{s.loading ? <span className="dim"> (reading again…)</span> : null}
|
|
92
|
-
</div>
|
|
93
|
-
<div className="pfilters">
|
|
94
|
-
<input placeholder="filter by word or rule" value={s.word} onChange={(e) => s.setWord(e.target.value)} />
|
|
95
|
-
<input placeholder="filter by file" list="prose-files" value={s.file} onChange={(e) => s.setFile(e.target.value)} />
|
|
96
|
-
<datalist id="prose-files">{s.files.map((f) => <option key={f} value={f} />)}</datalist>
|
|
97
|
-
<select className="wsel" value={s.tier} onChange={(e) => s.setTier(e.target.value as TierFilter)}>
|
|
98
|
-
{TIERS.map((t) => <option key={t.v} value={t.v}>{t.label}</option>)}
|
|
99
|
-
</select>
|
|
100
|
-
{s.word !== '' || s.file !== '' || s.tier !== 'all' ? <span className="dim">{s.shownCount} shown</span> : null}
|
|
101
|
-
</div>
|
|
102
|
-
{groups.length === 0 ? <div className="dim">no hit matches the filters</div> : null}
|
|
103
|
-
{drawn.map((g) => <Group key={g.key} app={app} s={s} g={g} />)}
|
|
104
|
-
{drawn.length < groups.length
|
|
105
|
-
? <button className="pmore" onClick={() => s.showAllGroups()}>show the other {groups.length - drawn.length} words and rules</button>
|
|
106
|
-
: null}
|
|
107
|
-
</>
|
|
108
|
-
)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const ListRows = ({ s, words }: { s: ProseHitsSt; words: ProseWords }) => {
|
|
112
|
-
const rows = [
|
|
113
|
-
...(words.allow ?? []).map((a) => ({ kind: 'allow' as ListKind, word: a.word, to: undefined as string | undefined, reason: a.reason })),
|
|
114
|
-
...(words.ban ?? []).map((b) => ({ kind: 'ban' as ListKind, word: b.word, to: b.to, reason: b.reason })),
|
|
115
|
-
]
|
|
116
|
-
if (rows.length === 0) return <div className="dim">no entry: the house dictionary decides every word here</div>
|
|
117
|
-
return (
|
|
118
|
-
<div className="plist">
|
|
119
|
-
{rows.map((r) => (
|
|
120
|
-
<div key={`${r.kind}:${r.word}`} className="prow">
|
|
121
|
-
<span className={`stchip ${r.kind === 'allow' ? 'ok' : 'miss'}`}>{r.kind}</span>
|
|
122
|
-
<b>{r.word}</b>
|
|
123
|
-
{r.to == null || r.to === '' ? null : <span>→ {r.to}</span>}
|
|
124
|
-
{r.reason == null || r.reason === '' ? null : <span className="dim">{r.reason}</span>}
|
|
125
|
-
<span className="sp" />
|
|
126
|
-
<button disabled={s.busy} onClick={() => void s.remove(r.word)}>drop it</button>
|
|
127
|
-
</div>
|
|
128
|
-
))}
|
|
129
|
-
</div>
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const Lists = observer(({ app, s }: { app: RepoSt; s: ProseHitsSt }) => {
|
|
134
|
-
const f = s.form
|
|
135
|
-
const g = app.checks.gate
|
|
136
|
-
return (
|
|
137
|
-
<>
|
|
138
|
-
<ListRows s={s} words={s.view?.words ?? {}} />
|
|
139
|
-
<div className="pform">
|
|
140
|
-
<select className="wsel" value={f.kind} onChange={(e) => s.setForm('kind', e.target.value as ListKind)}>
|
|
141
|
-
<option value="allow">allow</option>
|
|
142
|
-
<option value="ban">ban</option>
|
|
143
|
-
</select>
|
|
144
|
-
<input placeholder={f.kind === 'allow' ? 'word or phrase this repo accepts' : 'word or phrase this repo refuses'} value={f.word} onChange={(e) => s.setForm('word', e.target.value)} />
|
|
145
|
-
{f.kind === 'ban' ? <input placeholder="write this instead" value={f.to} onChange={(e) => s.setForm('to', e.target.value)} /> : null}
|
|
146
|
-
<input placeholder="why (optional, it rides in the config)" value={f.reason} onChange={(e) => s.setForm('reason', e.target.value)} />
|
|
147
|
-
<button disabled={s.busy || f.word.trim() === ''} onClick={() => void s.submit()}>{f.kind === 'allow' ? 'allow it' : 'ban it'}</button>
|
|
148
|
-
</div>
|
|
149
|
-
{g?.wordsState === 'stale' ? <div className="warnline">the words block in {g.path} was hand edited: reconcile it, then the lists work again</div> : null}
|
|
150
|
-
{g != null && g.dirty ? <div className="note">{g.path} is edited and not committed, review it in your git client</div> : null}
|
|
151
|
-
{app.checks.gateNote === '' ? null : <div className="note">{app.checks.gateNote}</div>}
|
|
152
|
-
</>
|
|
153
|
-
)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
/** the two sections over one state, the part a test renders headless */
|
|
157
|
-
export const ProseSections = observer(({ app, s }: { app: RepoSt; s: ProseHitsSt }) => (
|
|
158
|
-
<>
|
|
159
|
-
<section className="csec">
|
|
160
|
-
<div className="cstitle">hits<Help text="every hit of the last read, grouped by the word or the rule that fired, the noisiest first. A file:line opens the editor on that line" /></div>
|
|
161
|
-
<Hits app={app} s={s} />
|
|
162
|
-
</section>
|
|
163
|
-
<section className="csec">
|
|
164
|
-
<div className="cstitle">this repo's words<Help text="allow drops every hit on a word or phrase, ban refuses an extra one and names what to write instead. The house dictionary stays the default. An edit writes repo.config.ts, reruns the check and leaves the file for you to commit" /></div>
|
|
165
|
-
<Lists app={app} s={s} />
|
|
166
|
-
</section>
|
|
167
|
-
</>
|
|
168
|
-
))
|
|
169
|
-
|
|
170
|
-
/** mounted by the check page for SK014 only: the state lives while the page
|
|
171
|
-
* is open, and reads the hits once on the way in */
|
|
172
|
-
export const ProseHits = observer(({ app }: { app: RepoSt }) => {
|
|
173
|
-
const s = useMemo(() => new ProseHitsSt(app), [app])
|
|
174
|
-
useEffect(() => { void s.load() }, [s])
|
|
175
|
-
return <ProseSections app={app} s={s} />
|
|
176
|
-
})
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/* the SK014 page: word groups, their hits, the repo's word lists (prose-hits-view.tsx) */
|
|
2
|
-
.pfilters{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center;margin-bottom:var(--s2)}
|
|
3
|
-
.pfilters input,.pform input{background:var(--bg);border:1px solid var(--line);border-radius:4px;color:var(--ink);
|
|
4
|
-
padding:var(--s1) var(--s2);font:inherit;font-size:12.5px}
|
|
5
|
-
.pfilters input:focus,.pform input:focus{border-color:var(--accent);outline:none}
|
|
6
|
-
.pgroup{border-top:1px solid var(--line)}
|
|
7
|
-
.phead{display:flex;gap:var(--s2);align-items:baseline;padding:var(--s1) 0;min-width:0}
|
|
8
|
-
.phead .sp,.prow .sp{flex:1}
|
|
9
|
-
.phead .pwhy{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
|
|
10
|
-
.ptoggle{background:none;border:0;color:var(--dim);cursor:pointer;padding:0;font:inherit}
|
|
11
|
-
.pcount{min-width:3.5em;text-align:right}
|
|
12
|
-
.pword{font-weight:500}
|
|
13
|
-
.phits{padding:0 0 var(--s2) var(--s4)}
|
|
14
|
-
.phit{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:baseline;padding:var(--s0) 0;font-size:12.5px}
|
|
15
|
-
.phit a{text-decoration:none}
|
|
16
|
-
.pexc{color:var(--dim);word-break:break-word}
|
|
17
|
-
.pexc mark{background:var(--hi2);color:var(--ink);border-bottom:1px solid var(--red);padding:0 var(--s0)}
|
|
18
|
-
.pmore{margin:var(--s1) 0}
|
|
19
|
-
.plist{display:flex;flex-direction:column;gap:var(--s1);margin-bottom:var(--s2)}
|
|
20
|
-
.prow{display:flex;gap:var(--s2);align-items:baseline}
|
|
21
|
-
.pform{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center}
|