@linxin666/dsh-client-ui-task-board 0.1.19 → 0.2.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.
Files changed (57) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +82 -104
  3. package/README.zh.md +82 -104
  4. package/lib/client.js +589 -602
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2187 -6
  7. package/lib/types/client/PluginSettingsCard.d.ts +6 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +7 -4
  9. package/lib/types/client/board/TaskCard.d.ts +5 -2
  10. package/lib/types/client/board/status-key.d.ts +4 -0
  11. package/lib/types/client/host-api.d.ts +18 -0
  12. package/lib/types/client/locales.d.ts +10 -0
  13. package/lib/types/core/controller.d.ts +40 -15
  14. package/lib/types/core/schedule.d.ts +4 -2
  15. package/lib/types/core/tasks.d.ts +7 -5
  16. package/lib/types/core/use-cases/task-archive.d.ts +5 -4
  17. package/lib/types/core/use-cases/task-schedule.d.ts +3 -3
  18. package/lib/types/dsh-home.d.ts +16 -0
  19. package/lib/types/host-ledger.d.ts +42 -0
  20. package/lib/types/host-routes.d.ts +24 -0
  21. package/lib/types/host-runner.d.ts +30 -0
  22. package/lib/types/host-service.d.ts +41 -0
  23. package/lib/types/index.d.ts +20 -10
  24. package/lib/types/loopback.d.ts +24 -0
  25. package/lib/types/mount-once.d.ts +24 -0
  26. package/lib/types/power-inhibitor.d.ts +73 -0
  27. package/lib/types/protocol.d.ts +72 -0
  28. package/package.json +12 -9
  29. package/src/client/PluginSettingsCard.tsx +10 -0
  30. package/src/client/TaskBoardSettingsCard.tsx +51 -4
  31. package/src/client/board/NewTaskModal.tsx +9 -6
  32. package/src/client/board/TaskBoard.tsx +22 -13
  33. package/src/client/board/TaskCard.tsx +24 -8
  34. package/src/client/board/TaskDetail.tsx +84 -59
  35. package/src/client/board/status-key.ts +11 -0
  36. package/src/client/host-api.ts +95 -0
  37. package/src/client/index.ts +9 -78
  38. package/src/client/locales.ts +24 -4
  39. package/src/core/controller.ts +205 -24
  40. package/src/core/schedule.ts +20 -3
  41. package/src/core/store.ts +5 -14
  42. package/src/core/tasks.ts +7 -7
  43. package/src/core/use-cases/task-archive.ts +14 -5
  44. package/src/core/use-cases/task-schedule.ts +6 -5
  45. package/src/core/use-cases/task-update.ts +1 -6
  46. package/src/dsh-home.ts +36 -0
  47. package/src/host-ledger.ts +478 -0
  48. package/src/host-routes.ts +188 -0
  49. package/src/host-runner.ts +134 -0
  50. package/src/host-service.ts +209 -0
  51. package/src/index.ts +58 -12
  52. package/src/loopback.ts +63 -0
  53. package/src/mount-once.ts +48 -0
  54. package/src/power-inhibitor.ts +321 -0
  55. package/src/protocol.ts +213 -0
  56. package/lib/types/core/scheduler.d.ts +0 -67
  57. package/src/core/scheduler.ts +0 -151
@@ -1,151 +0,0 @@
1
- /**
2
- * Browser-side scheduler: the heartbeat behind scheduled task runs.
3
- *
4
- * The board is a pure client plugin with no server channel, so "定时任务"
5
- * lives in the tab: a timer ticks every minute (plus immediately on tab
6
- * visibility recovery) and triggers any task whose `schedule.nextRunAt` is
7
- * due, rolling the schedule forward to the next cron match before triggering
8
- * so the same tick never double-fires. Missed runs are skipped, never queued:
9
- * a task still running at its due instant is skipped by the controller's
10
- * runTask guard and simply waits for the next cron match.
11
- *
12
- * A deleted task can never fire again from a stale in-memory copy: every
13
- * tick first re-reads the persisted ledger through the optional `refresh`
14
- * hook (the controller reloads its store), so the fire decision and the
15
- * subsequent roll-forward both run on the freshest persisted truth.
16
- *
17
- * The ticker is controlled: `start` arms a single interval guarded by an
18
- * idempotence check (a second start while running is a no-op, so wiring can
19
- * never leak a duplicate timer), and `stop`/`dispose` clear the timer and
20
- * drop the environment listeners. The board unmount path calls stop/dispose,
21
- * so no interval or listener outlives the board.
22
- *
23
- * Framework-free: all runtime access flows through the injected deps
24
- * (structural faces), so tests drive ticks directly without timers.
25
- */
26
- import { nextRunAtMs } from './schedule.ts'
27
- import type { TaskRecord } from './tasks.ts'
28
-
29
- /** Everything the scheduler needs from its host (the board controller). */
30
- export interface SchedulerDeps {
31
- /** Read the current task ledger (the controller snapshot). */
32
- tasks(): readonly TaskRecord[]
33
- /**
34
- * Re-sync the task ledger from its persisted source before the tick decides
35
- * what is due. The wiring passes the controller's store reload so a task
36
- * deleted elsewhere can never be fired from a stale in-memory copy.
37
- * Optional: absent in pure in-memory harnesses.
38
- */
39
- refresh?: () => void
40
- /** Clock; defaults to Date.now in the controller wiring. */
41
- now(): number
42
- /** Trigger one task's real execution (the controller's runTask); resolves
43
- * true when the run was accepted, false when rejected (e.g. the task is
44
- * already running). */
45
- runTask(id: string): Promise<boolean>
46
- /** Persist a rolled-forward schedule (next due + this trigger instant). */
47
- applySchedule(id: string, nextRunAt: number | undefined, lastTriggeredAt: number | undefined): void
48
- /** Tick cadence; defaults to 60_000 ms. */
49
- tickMs?: number
50
- /**
51
- * Gate: while false the tick no-ops (e.g. the session list baseline has not
52
- * arrived on page load, so executions would fail). Defaults to always ready.
53
- */
54
- ready?: () => boolean
55
- /** Environment listeners for tab-visibility recovery (browser only). */
56
- environment?: {
57
- addEventListener(type: 'visibilitychange', listener: () => void): void
58
- removeEventListener(type: 'visibilitychange', listener: () => void): void
59
- }
60
- }
61
-
62
- /**
63
- * The schedule heartbeat (see module doc). `tick` is public so tests and
64
- * callers can drive a check without waiting for the interval.
65
- */
66
- export class SchedulerService {
67
- private timer: ReturnType<typeof setInterval> | undefined
68
- private environmentListener: (() => void) | undefined
69
- private disposed = false
70
- private started = false
71
-
72
- /** @param deps - tasks/clock/trigger/apply faces (see {@link SchedulerDeps}). */
73
- constructor(private readonly deps: SchedulerDeps) {}
74
-
75
- /**
76
- * Start ticking: one immediate check (catch-up after reload) + the interval.
77
- * Single-instance: a second start while already armed is a no-op, so a
78
- * re-entrant mount can never stack a duplicate timer.
79
- */
80
- start(): void {
81
- if (this.disposed) return
82
- if (this.started) return
83
- this.started = true
84
- // Immediate catch-up tick: schedules whose due instant passed while the
85
- // tab was closed are triggered as soon as the runtime is ready.
86
- this.tick()
87
- this.timer = setInterval(() => { this.tick() }, this.deps.tickMs ?? 60_000)
88
- if (this.deps.environment !== undefined) {
89
- this.environmentListener = () => { this.tick() }
90
- this.deps.environment.addEventListener('visibilitychange', this.environmentListener)
91
- }
92
- }
93
-
94
- /**
95
- * Stop ticking and drop listeners (idempotent). Preferred shutdown verb for
96
- * callers that treat the scheduler as a controlled ticker; `dispose` is an
97
- * alias.
98
- */
99
- stop(): void {
100
- this.dispose()
101
- }
102
-
103
- /** Stop ticking and drop listeners (idempotent). */
104
- dispose(): void {
105
- if (this.disposed && this.timer === undefined && this.environmentListener === undefined) return
106
- this.disposed = true
107
- this.started = false
108
- if (this.timer !== undefined) {
109
- clearInterval(this.timer)
110
- this.timer = undefined
111
- }
112
- if (this.environmentListener !== undefined && this.deps.environment !== undefined) {
113
- this.deps.environment.removeEventListener('visibilitychange', this.environmentListener)
114
- this.environmentListener = undefined
115
- }
116
- }
117
-
118
- /**
119
- * Check every enabled schedule and trigger the due ones. Idempotent per
120
- * task per tick: the schedule is rolled forward only after runTask accepts
121
- * the run, so a rejected run keeps its due slot and is retried on the next
122
- * tick instead of being silently dropped.
123
- */
124
- async tick(): Promise<void> {
125
- if (this.disposed) return
126
- if (this.deps.ready !== undefined && !this.deps.ready()) return
127
- // Freshest persisted truth before any fire decision: a task deleted in
128
- // another tab (or a stale in-memory copy) must never be triggered.
129
- this.deps.refresh?.()
130
- const now = this.deps.now()
131
- for (const task of this.deps.tasks()) {
132
- const schedule = task.schedule
133
- if (schedule === undefined || !schedule.enabled) continue
134
- if (schedule.nextRunAt === undefined) {
135
- // Missing next-run instant (repaired/legacy data): recompute from the
136
- // cron expression and wait; an unparseable expression is skipped.
137
- const repaired = nextRunAtMs(schedule.cron, now)
138
- if (repaired === undefined) continue
139
- this.deps.applySchedule(task.id, repaired, undefined)
140
- continue
141
- }
142
- if (schedule.nextRunAt > now) continue
143
- // Advance from the due instant (not this tick's wall-clock) and only
144
- // after the run is accepted: a rejected run keeps its due slot.
145
- const next = nextRunAtMs(schedule.cron, schedule.nextRunAt)
146
- const accepted = await this.deps.runTask(task.id)
147
- if (accepted) this.deps.applySchedule(task.id, next, now)
148
- }
149
- }
150
- }
151
-