@knpkv/jcf-web 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.
- package/LICENSE +21 -0
- package/README.md +315 -0
- package/dist/client/assets/AgentSettingsPanel-NhTzwiq6.js +1 -0
- package/dist/client/assets/Schema-DUFTUil2.js +2 -0
- package/dist/client/assets/UrlParams-ZN2s8OM_.js +6 -0
- package/dist/client/assets/agentSettings-nw9lCgBQ.js +1 -0
- package/dist/client/assets/api-BQCWhiKV.js +4 -0
- package/dist/client/assets/decoding-CebF86HD.js +1 -0
- package/dist/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/client/assets/index-Ch0W9DhA.css +1 -0
- package/dist/client/assets/index-N2vhoKYl.js +51 -0
- package/dist/client/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/dist/client/assets/transport-AcByYfLh.js +1 -0
- package/dist/client/index.html +19 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +32 -0
- package/dist/main.js.map +1 -0
- package/dist/server/Api.d.ts +789 -0
- package/dist/server/Api.js +98 -0
- package/dist/server/Api.js.map +1 -0
- package/dist/server/Confirm.d.ts +94 -0
- package/dist/server/Confirm.js +243 -0
- package/dist/server/Confirm.js.map +1 -0
- package/dist/server/Handlers.d.ts +14 -0
- package/dist/server/Handlers.js +157 -0
- package/dist/server/Handlers.js.map +1 -0
- package/dist/server/HttpApplication.d.ts +7 -0
- package/dist/server/HttpApplication.js +62 -0
- package/dist/server/HttpApplication.js.map +1 -0
- package/dist/server/OwnerSession.d.ts +62 -0
- package/dist/server/OwnerSession.js +219 -0
- package/dist/server/OwnerSession.js.map +1 -0
- package/dist/server/RowDescriptions.d.ts +19 -0
- package/dist/server/RowDescriptions.js +42 -0
- package/dist/server/RowDescriptions.js.map +1 -0
- package/dist/server/SavedEntryOperations.d.ts +27 -0
- package/dist/server/SavedEntryOperations.js +171 -0
- package/dist/server/SavedEntryOperations.js.map +1 -0
- package/dist/server/Server.d.ts +13 -0
- package/dist/server/Server.js +29 -0
- package/dist/server/Server.js.map +1 -0
- package/dist/server/WeekPlan.d.ts +110 -0
- package/dist/server/WeekPlan.js +258 -0
- package/dist/server/WeekPlan.js.map +1 -0
- package/dist/server/WeekPlans.d.ts +30 -0
- package/dist/server/WeekPlans.js +190 -0
- package/dist/server/WeekPlans.js.map +1 -0
- package/dist/server/WeekRead.d.ts +435 -0
- package/dist/server/WeekRead.js +189 -0
- package/dist/server/WeekRead.js.map +1 -0
- package/dist/shared/contracts.d.ts +901 -0
- package/dist/shared/contracts.js +365 -0
- package/dist/shared/contracts.js.map +1 -0
- package/dist/shared/writePlanning.d.ts +6 -0
- package/dist/shared/writePlanning.js +19 -0
- package/dist/shared/writePlanning.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { AgentSessions, ReconcileService, SourceConsumption, Time } from "@knpkv/jira-clockify";
|
|
2
|
+
import { Clock, Effect } from "effect";
|
|
3
|
+
import { ApiError, PlanExpiredError, ProposalRejectedError } from "../shared/contracts.js";
|
|
4
|
+
import { MINIMUM_WRITE_SECONDS } from "../shared/writePlanning.js";
|
|
5
|
+
import { buildWeekPlan } from "./WeekPlan.js";
|
|
6
|
+
/** Metadata must agree with its retained provider. A description edit may change its ticket prefix. */
|
|
7
|
+
export const findSavedEntry = (plan, request) => {
|
|
8
|
+
if (plan.plan.scope !== "both" && plan.plan.scope !== request.source)
|
|
9
|
+
return undefined;
|
|
10
|
+
const matches = (entry) => entry.source === request.source && entry.id === request.entryId;
|
|
11
|
+
for (const row of plan.plan.rows) {
|
|
12
|
+
for (const interval of row.intervals) {
|
|
13
|
+
if (interval.entry !== undefined && interval.source === request.source &&
|
|
14
|
+
matches(interval.entry))
|
|
15
|
+
return interval.entry;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return plan.plan.unlinkedClockify.find(({ entry }) => entry !== undefined && entry.source === "clockify" && matches(entry))?.entry;
|
|
19
|
+
};
|
|
20
|
+
/** Provider refreshes keep the handle only when every expected field still describes the same entry. */
|
|
21
|
+
export const retainedEntryRevision = (plan, entry, fresh) => {
|
|
22
|
+
const previous = findSavedEntry(plan, { planId: plan.planId, source: entry.source, entryId: entry.id });
|
|
23
|
+
return previous !== undefined && previous.source === entry.source && previous.id === entry.id &&
|
|
24
|
+
previous.ticketKey === entry.ticketKey && previous.startMs === entry.startMs &&
|
|
25
|
+
previous.endMs === entry.endMs && previous.description === entry.description
|
|
26
|
+
? previous.revision
|
|
27
|
+
: fresh;
|
|
28
|
+
};
|
|
29
|
+
/** Rebuild provider slices and proposal deltas locally so a failed totals refresh cannot undo a save. */
|
|
30
|
+
export const replaceSavedEntry = (plan, entry) => {
|
|
31
|
+
const matches = (candidate) => candidate?.source === entry.source && candidate.id === entry.id;
|
|
32
|
+
const seconds = (interval) => Math.round((interval.endMs - interval.startMs) / 1000);
|
|
33
|
+
const tally = (source) => plan.report.recorded.flatMap((row) => {
|
|
34
|
+
const intervals = row.intervals.filter((interval) => interval.source === source);
|
|
35
|
+
const remaining = intervals.filter((interval) => !matches(interval.entry)).map((interval) => ({
|
|
36
|
+
...interval,
|
|
37
|
+
day: row.day,
|
|
38
|
+
ticketKey: row.ticketKey,
|
|
39
|
+
seconds: seconds(interval),
|
|
40
|
+
description: interval.entry === undefined
|
|
41
|
+
? (source === "clockify" ? row.clockifyDescription : null)
|
|
42
|
+
: interval.entry.description
|
|
43
|
+
}));
|
|
44
|
+
const residual = (source === "clockify" ? row.clockifySeconds : row.jiraSeconds) -
|
|
45
|
+
intervals.reduce((sum, interval) => sum + seconds(interval), 0);
|
|
46
|
+
return residual === 0
|
|
47
|
+
? remaining
|
|
48
|
+
: [...remaining, {
|
|
49
|
+
day: row.day,
|
|
50
|
+
ticketKey: row.ticketKey,
|
|
51
|
+
seconds: residual,
|
|
52
|
+
description: source === "clockify" ? row.clockifyDescription : null
|
|
53
|
+
}];
|
|
54
|
+
});
|
|
55
|
+
const clockify = tally("clockify");
|
|
56
|
+
const jira = tally("jira");
|
|
57
|
+
const unlinked = plan.report.unlinkedClockify.filter((slice) => !matches(slice.entry));
|
|
58
|
+
for (const { day, endMs, seconds: sliceSeconds, startMs } of Time.splitIntervalByLocalDay(entry.startMs, entry.endMs)) {
|
|
59
|
+
const slice = {
|
|
60
|
+
entry,
|
|
61
|
+
startMs,
|
|
62
|
+
endMs,
|
|
63
|
+
day,
|
|
64
|
+
seconds: sliceSeconds,
|
|
65
|
+
description: entry.description
|
|
66
|
+
};
|
|
67
|
+
if (entry.ticketKey !== null) {
|
|
68
|
+
const target = entry.source === "clockify" ? clockify : jira;
|
|
69
|
+
target.push({ ...slice, ticketKey: entry.ticketKey });
|
|
70
|
+
}
|
|
71
|
+
else if (entry.source === "clockify") {
|
|
72
|
+
unlinked.push(slice);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const recorded = ReconcileService.buildReconcileRows(clockify, jira);
|
|
76
|
+
const sourceEntries = plan.report.sourceEntries?.map((source) => source.source === entry.source && source.id === entry.id
|
|
77
|
+
? { ...source, startMs: entry.startMs, endMs: entry.endMs }
|
|
78
|
+
: source);
|
|
79
|
+
const report = {
|
|
80
|
+
...plan.report,
|
|
81
|
+
recorded,
|
|
82
|
+
unlinkedClockify: unlinked,
|
|
83
|
+
sourceEntries,
|
|
84
|
+
proposals: AgentSessions.buildSessionProposals(plan.report.attributed, recorded, {
|
|
85
|
+
minimumSeconds: MINIMUM_WRITE_SECONDS,
|
|
86
|
+
excludedDays: plan.report.excludedDays.map((excluded) => excluded.day),
|
|
87
|
+
sides: plan.report.sides,
|
|
88
|
+
sourceEntries,
|
|
89
|
+
consumptionRows: unlinked.map((slice) => ({
|
|
90
|
+
day: slice.day,
|
|
91
|
+
intervals: slice.entry === undefined ? [] : [{ entry: slice.entry }]
|
|
92
|
+
}))
|
|
93
|
+
})
|
|
94
|
+
};
|
|
95
|
+
return buildWeekPlan({
|
|
96
|
+
planId: plan.planId,
|
|
97
|
+
createdAtMillis: plan.createdAtMillis,
|
|
98
|
+
monday: new Date(`${plan.plan.monday}T00:00:00`),
|
|
99
|
+
scope: plan.plan.scope,
|
|
100
|
+
sessionScanAvailable: plan.plan.sessionScanAvailable,
|
|
101
|
+
consumption: plan.consumption,
|
|
102
|
+
jiraReceiptIssueKeys: plan.jiraReceiptIssueKeys,
|
|
103
|
+
ownership: plan.ownership,
|
|
104
|
+
entryRevision: (candidate) => matches(candidate) ? entry.revision : findSavedEntry(plan, {
|
|
105
|
+
planId: plan.planId,
|
|
106
|
+
source: candidate.source,
|
|
107
|
+
entryId: candidate.id
|
|
108
|
+
})?.revision ?? plan.planId,
|
|
109
|
+
report
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
/** A changed interval stays in the retained week and is at most one day. Long unchanged entries remain editable. */
|
|
113
|
+
export const validateSavedUpdate = Effect.fn("SavedEntryOperations.validate")(function* (plan, expected, request) {
|
|
114
|
+
const changed = expected.startMs !== request.startMs || expected.endMs !== request.endMs;
|
|
115
|
+
if (changed) {
|
|
116
|
+
const nowMs = yield* Clock.currentTimeMillis;
|
|
117
|
+
if (request.startMs > nowMs || request.endMs > nowMs) {
|
|
118
|
+
return yield* new ProposalRejectedError({ message: "Choose an interval that has already ended." });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const period = Time.isoWeekPeriod(new Date(`${plan.plan.monday}T00:00:00`));
|
|
122
|
+
if (changed && expected.source === "jira" && request.startMs < period.from.getTime()) {
|
|
123
|
+
return yield* new ProposalRejectedError({
|
|
124
|
+
message: "Choose a Jira worklog start within this week."
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (request.endMs <= request.startMs ||
|
|
128
|
+
(changed && (request.endMs - request.startMs > 86400000 || request.endMs <= period.from.getTime() ||
|
|
129
|
+
request.startMs >= period.to.getTime()))) {
|
|
130
|
+
return yield* new ProposalRejectedError({
|
|
131
|
+
message: "Choose a positive interval of at most 24 hours overlapping this week."
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
export const savedEntryFailure = (error) => {
|
|
136
|
+
switch (error.reason) {
|
|
137
|
+
case "validation":
|
|
138
|
+
return new ProposalRejectedError({ message: error.message });
|
|
139
|
+
case "conflict":
|
|
140
|
+
return new PlanExpiredError({ message: error.message });
|
|
141
|
+
case "provider":
|
|
142
|
+
return new ApiError({ message: error.message });
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
/** Only individually overlapping sessions qualify. An unkeyed entry with several matches is ambiguous. */
|
|
146
|
+
export const describeSavedEntry = Effect.fn("SavedEntryOperations.describe")(function* (options) {
|
|
147
|
+
const { attributor, entry, plan } = options;
|
|
148
|
+
const sessions = (plan.report.sessionEvidence ?? []).filter((session) => (entry.ticketKey === null || session.ticketKey === entry.ticketKey) &&
|
|
149
|
+
session.spans.some((span) => span.startMs < entry.endMs && span.endMs > entry.startMs) &&
|
|
150
|
+
(plan.report.digests.get(session.sessionId)?.trim().length ?? 0) > 0);
|
|
151
|
+
const ids = [...new Set(sessions.map((session) => session.sessionId))];
|
|
152
|
+
if (ids.length === 0 || (entry.ticketKey === null && ids.length !== 1))
|
|
153
|
+
return { note: null, sessionCount: 0 };
|
|
154
|
+
const digest = ids.map((id) => plan.report.digests.get(id)).join("\n\n").slice(0, 24000);
|
|
155
|
+
const id = `${entry.source}:${entry.id}`;
|
|
156
|
+
const answers = yield* attributor.describe([{
|
|
157
|
+
id,
|
|
158
|
+
ticketKey: entry.ticketKey,
|
|
159
|
+
summary: entry.ticketKey === null ? null : plan.ownership.facts.get(entry.ticketKey)?.title ?? null,
|
|
160
|
+
digest
|
|
161
|
+
}]).pipe(Effect.mapError((error) => new ApiError({ message: `Description generation failed: ${error.message}` })));
|
|
162
|
+
const summary = answers.find((answer) => answer.id === id)?.note;
|
|
163
|
+
if (summary == null || summary.trim() === "")
|
|
164
|
+
return { note: null, sessionCount: ids.length };
|
|
165
|
+
const prefix = entry.source === "clockify" && entry.ticketKey !== null ? `[${entry.ticketKey}] ` : "";
|
|
166
|
+
const suffix = SourceConsumption.markers(entry.description ?? "")[0] ?? "";
|
|
167
|
+
const reserved = suffix === "" ? 0 : suffix.length + 1;
|
|
168
|
+
const note = `${prefix}${summary.slice(0, Math.max(0, 500 - prefix.length - reserved))}`;
|
|
169
|
+
return { note: suffix === "" ? note : `${note}\n${suffix}`, sessionCount: ids.length };
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=SavedEntryOperations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SavedEntryOperations.js","sourceRoot":"","sources":["../../src/server/SavedEntryOperations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Config, Deferred, Layer, Schema } from "effect";
|
|
2
|
+
import { type OwnerSessionSecretsContract } from "./OwnerSession.js";
|
|
3
|
+
export interface JcfWebServerOptions {
|
|
4
|
+
readonly hostname?: string;
|
|
5
|
+
readonly port: number;
|
|
6
|
+
readonly ready?: Deferred.Deferred<void>;
|
|
7
|
+
readonly security: OwnerSessionSecretsContract;
|
|
8
|
+
}
|
|
9
|
+
export declare const makeServer: (options: JcfWebServerOptions) => Layer.Layer<never, import("@knpkv/browser-pairing").BrowserPairingError | Config.ConfigError | Schema.SchemaError | import("effect/http/HttpServerError").ServeError, never>;
|
|
10
|
+
/** The port to bind. Deliberately not 3000: the CodeCommit web app already lives there. */
|
|
11
|
+
export declare const Port: Config.Config<number>;
|
|
12
|
+
/** Set by `pnpm dev` so the printed URL points at the Vite dev server that proxies here. */
|
|
13
|
+
export declare const PublicOrigin: Config.Config<import("effect/Option").Option<string>>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding the week view to a loopback port.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **One process, one operator, one origin.** The server listens on loopback, mints its own session
|
|
7
|
+
* at startup, and prints the URL that spends it. There is nothing to configure and nobody to
|
|
8
|
+
* authenticate against.
|
|
9
|
+
* - **The engine is the same one the CLI runs.** `HeadlessLayer` from `@knpkv/jira-clockify` is
|
|
10
|
+
* provided whole, so a week read here and a `jcf sync reconcile --agent` in a terminal derive from
|
|
11
|
+
* the same services against the same config.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { NodeHttpServer, NodeServices } from "@effect/platform-node";
|
|
16
|
+
import { Layers } from "@knpkv/jira-clockify";
|
|
17
|
+
import { Config, Deferred, Effect, Layer, Schema } from "effect";
|
|
18
|
+
import { Etag, HttpPlatform, HttpRouter } from "effect/http";
|
|
19
|
+
import { createServer } from "node:http";
|
|
20
|
+
import { application } from "./HttpApplication.js";
|
|
21
|
+
import { activateOwnerSessionBootstrap, OwnerSessionSecrets } from "./OwnerSession.js";
|
|
22
|
+
const HttpPlatformLive = HttpPlatform.layer.pipe(Layer.provide(NodeServices.layer));
|
|
23
|
+
const LoopbackHostname = Schema.Literals(["127.0.0.1", "localhost", "::1"]);
|
|
24
|
+
export const makeServer = (options) => Layer.unwrap(Schema.decodeUnknownEffect(LoopbackHostname)(options.hostname ?? "127.0.0.1").pipe(Effect.map((hostname) => HttpRouter.serve(application.pipe(Layer.provide(Layers.HeadlessLayer))).pipe(Layer.provide(NodeHttpServer.layerServer(createServer, { host: hostname, port: options.port })), Layer.provide(Etag.layer), Layer.provide(HttpPlatformLive), Layer.provide(NodeServices.layer), Layer.provide(Layer.succeed(OwnerSessionSecrets, options.security)), Layer.tap(() => activateOwnerSessionBootstrap(options.security).pipe(Effect.andThen(options.ready === undefined ? Effect.void : Deferred.succeed(options.ready, undefined))))))));
|
|
25
|
+
/** The port to bind. Deliberately not 3000: the CodeCommit web app already lives there. */
|
|
26
|
+
export const Port = Config.Int("PORT").pipe(Config.withDefault(3111));
|
|
27
|
+
/** Set by `pnpm dev` so the printed URL points at the Vite dev server that proxies here. */
|
|
28
|
+
export const PublicOrigin = Config.option(Config.String("JCF_WEB_PUBLIC_ORIGIN"));
|
|
29
|
+
//# sourceMappingURL=Server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../src/server/Server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,6BAA6B,EAAE,mBAAmB,EAAoC,MAAM,mBAAmB,CAAA;AAExH,MAAM,gBAAgB,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;AACnF,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAA;AAS3E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAA4B,EAAE,EAAE,CACzD,KAAK,CAAC,MAAM,CACV,MAAM,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CACzG,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAC1E,KAAK,CAAC,OAAO,CACX,cAAc,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CACjF,EACD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EACzB,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAC/B,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EACjC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,EACnE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CACb,6BAA6B,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAClD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CACvG,CACF,CACF,CACF,CAAC,CACH,CAAA;AAEH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;AAErE,4FAA4F;AAC5F,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning one reconciliation report into the week the browser draws, and holding the evidence.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **One read answers the whole week.** A report already carries what both systems hold and what
|
|
7
|
+
* the sessions evidence, so allocated time and proposable time are two views of one payload rather
|
|
8
|
+
* than two round trips that could disagree.
|
|
9
|
+
* - **A row is a `(Issue Key, day)` cell, and it exists if either side has anything.** A day tracked
|
|
10
|
+
* with a Timer has no proposal; a day nobody logged has no allocated seconds. Showing them in one
|
|
11
|
+
* grid is the point — the gap is only visible next to what is already there.
|
|
12
|
+
* - **The evidence stays here.** The wire carries spans so the browser can draw when work happened,
|
|
13
|
+
* but a confirmation names a row, and this module is what turns that name back into the engine's
|
|
14
|
+
* own proposal. Nothing a browser sends can widen it.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentSessions, ReconcileService } from "@knpkv/jira-clockify";
|
|
19
|
+
import { IssueFacts, SourceConsumption } from "@knpkv/jira-clockify";
|
|
20
|
+
import type { OwnershipMode, WeekPlanResponse, WeekScopeName } from "./Api.js";
|
|
21
|
+
/** How a confirmation names a row. Deterministic, so re-reading a week keeps the same names. */
|
|
22
|
+
export declare const rowId: (ticketKey: string, day: string) => string;
|
|
23
|
+
/** The seven local days of the ISO week beginning at `monday`. */
|
|
24
|
+
export declare const weekDays: (monday: Date) => ReadonlyArray<string>;
|
|
25
|
+
/** One row's proposal as the engine produced it, kept whole for the write path. */
|
|
26
|
+
export interface RowEvidence {
|
|
27
|
+
readonly rowId: string;
|
|
28
|
+
readonly proposal: AgentSessions.SessionProposal;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A plan the server still holds: what it sent, and the evidence behind every proposable row.
|
|
32
|
+
*
|
|
33
|
+
* Held rather than re-derived because deriving it reads every transcript in the window and may wake
|
|
34
|
+
* a Coding Agent. Confirming four rows would otherwise pay for that four more times — and each run
|
|
35
|
+
* could place a session differently from the plan the person was looking at.
|
|
36
|
+
*/
|
|
37
|
+
export interface HeldPlan {
|
|
38
|
+
/** Server-held attribution and ownership snapshot. Never accepted from a browser payload. */
|
|
39
|
+
readonly report: ReconcileService.SessionProposalReport;
|
|
40
|
+
readonly ownership: OwnershipInput;
|
|
41
|
+
readonly planId: string;
|
|
42
|
+
readonly createdAtMillis: number;
|
|
43
|
+
readonly plan: WeekPlanResponse;
|
|
44
|
+
readonly evidence: ReadonlyMap<string, RowEvidence>;
|
|
45
|
+
/** Server-only scopes bound by a complete read or first explicitly enabled confirmation. */
|
|
46
|
+
readonly boundScopes: {
|
|
47
|
+
clockify: string | null;
|
|
48
|
+
jira: string | null;
|
|
49
|
+
};
|
|
50
|
+
/** Source blocks consumed under corrected ticket labels, independently for each provider. */
|
|
51
|
+
readonly consumption: SourceConsumption.Consumption;
|
|
52
|
+
/** Jira targets with successful writes that bypass eventually consistent JQL discovery. */
|
|
53
|
+
readonly jiraReceiptIssueKeys: Set<string>;
|
|
54
|
+
}
|
|
55
|
+
/** In-memory allocation position; the durable provider binding still names only the source cluster. */
|
|
56
|
+
export declare const evidenceBlockKey: (row: string, block: AgentSessions.CreditedBlock) => string;
|
|
57
|
+
/** Durable source identity carried by entries written under a corrected ticket. */
|
|
58
|
+
export declare const evidenceMarker: (row: string, block: AgentSessions.CreditedBlock) => string;
|
|
59
|
+
/** Rebuild corrected-ticket consumption against the blocks in this read. */
|
|
60
|
+
export declare const reconcileConsumption: (report: ReconcileService.SessionProposalReport, previous?: HeldPlan["consumption"]) => HeldPlan["consumption"];
|
|
61
|
+
/** The systems a plan was read under — what a confirmation writes to unless it says otherwise. */
|
|
62
|
+
export declare const planSides: (plan: HeldPlan) => ReconcileService.ReconcileSides;
|
|
63
|
+
/**
|
|
64
|
+
* What Jira says about the tickets in a week, and whether it could be asked at all.
|
|
65
|
+
*
|
|
66
|
+
* Passed in rather than looked up here so this stays a pure function of one report plus one set of
|
|
67
|
+
* answers — which is what makes the ownership rule testable without a Jira anywhere near it.
|
|
68
|
+
*/
|
|
69
|
+
export interface OwnershipInput {
|
|
70
|
+
readonly facts: ReadonlyMap<string, IssueFacts.IssueFact>;
|
|
71
|
+
readonly checked: boolean;
|
|
72
|
+
readonly mode: OwnershipMode;
|
|
73
|
+
/** Issue Keys a person has said are theirs regardless. */
|
|
74
|
+
readonly overrides: ReadonlyArray<string>;
|
|
75
|
+
}
|
|
76
|
+
export declare const anyOwner: OwnershipInput;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a row may be proposed on this Issue Key.
|
|
79
|
+
*
|
|
80
|
+
* Four ways to be yours, and only one way not to be: the mode is `any`, the key is overridden, Jira
|
|
81
|
+
* says you are the assignee — or Jira was never asked. A row is withheld only on a positive answer
|
|
82
|
+
* that names somebody else, because withholding on silence is how hours disappear.
|
|
83
|
+
*/
|
|
84
|
+
export declare const isOwnedByMe: typeof IssueFacts.isOwnedByMe;
|
|
85
|
+
/** Which systems a scope name puts in play. */
|
|
86
|
+
export declare const sidesOfScope: (scope: WeekScopeName) => ReconcileService.ReconcileSides;
|
|
87
|
+
/** Reproject browser blocks when retention changes the authoritative consumed-time map. */
|
|
88
|
+
export declare const withProjectedConsumption: (held: HeldPlan, consumption: HeldPlan["consumption"]) => HeldPlan;
|
|
89
|
+
/**
|
|
90
|
+
* Build the week the browser draws from one report.
|
|
91
|
+
*
|
|
92
|
+
* Rows are keyed by `(Issue Key, day)` and sorted by Issue Key then day, so a re-read after a write
|
|
93
|
+
* puts every row back where the reader last saw it.
|
|
94
|
+
*/
|
|
95
|
+
export declare const buildWeekPlan: (options: {
|
|
96
|
+
readonly planId: string;
|
|
97
|
+
readonly monday: Date;
|
|
98
|
+
readonly createdAtMillis: number;
|
|
99
|
+
readonly scope: WeekScopeName;
|
|
100
|
+
readonly report: ReconcileService.SessionProposalReport;
|
|
101
|
+
/** What Jira says about these tickets. Omitted means nobody asked, so nothing is withheld. */
|
|
102
|
+
readonly ownership?: OwnershipInput | undefined;
|
|
103
|
+
/** Initial scans use their opaque plan ID; later snapshots supply fresh or retained entry handles. */
|
|
104
|
+
readonly entryRevision?: (entry: ReconcileService.RecordedEntry) => string;
|
|
105
|
+
/** Defaults to true for explicit scans; recorded-only reads must pass false. */
|
|
106
|
+
readonly sessionScanAvailable?: boolean | undefined;
|
|
107
|
+
readonly consumption?: HeldPlan["consumption"] | undefined;
|
|
108
|
+
readonly jiraReceiptIssueKeys?: HeldPlan["jiraReceiptIssueKeys"] | undefined;
|
|
109
|
+
}) => HeldPlan;
|
|
110
|
+
export { MINIMUM_WRITE_SECONDS, type ProposedWrite, proposeWrite, selectedBlocks } from "../shared/writePlanning.js";
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { IssueFacts, SourceConsumption, Time } from "@knpkv/jira-clockify";
|
|
2
|
+
/** How a confirmation names a row. Deterministic, so re-reading a week keeps the same names. */
|
|
3
|
+
export const rowId = (ticketKey, day) => `${day}:${ticketKey}`;
|
|
4
|
+
/** The seven local days of the ISO week beginning at `monday`. */
|
|
5
|
+
export const weekDays = (monday) => {
|
|
6
|
+
const days = [];
|
|
7
|
+
let atMs = monday.getTime();
|
|
8
|
+
for (let index = 0; index < 7; index++) {
|
|
9
|
+
days.push(Time.localDay(new Date(atMs)));
|
|
10
|
+
atMs = Time.nextLocalMidnight(atMs);
|
|
11
|
+
}
|
|
12
|
+
return days;
|
|
13
|
+
};
|
|
14
|
+
/** In-memory allocation position; the durable provider binding still names only the source cluster. */
|
|
15
|
+
export const evidenceBlockKey = (row, block) => JSON.stringify([row, block.sourceStartMs ?? block.startMs, block.allocationIndex ?? 0]);
|
|
16
|
+
/** Durable source identity carried by entries written under a corrected ticket. */
|
|
17
|
+
export const evidenceMarker = (row, block) => SourceConsumption.marker(row, block.sourceStartMs ?? block.startMs);
|
|
18
|
+
/** Rebuild corrected-ticket consumption against the blocks in this read. */
|
|
19
|
+
export const reconcileConsumption = (report, previous = new Map()) => {
|
|
20
|
+
const consumptionRows = [
|
|
21
|
+
...report.recorded,
|
|
22
|
+
...report.unlinkedClockify.map((slice) => ({
|
|
23
|
+
day: slice.day,
|
|
24
|
+
intervals: slice.entry === undefined ? [] : [{ entry: slice.entry }]
|
|
25
|
+
}))
|
|
26
|
+
];
|
|
27
|
+
const reconciled = new Map();
|
|
28
|
+
for (const credit of report.attributed) {
|
|
29
|
+
const recorded = report.recorded.find((row) => row.ticketKey === credit.ticketKey && row.day === credit.day);
|
|
30
|
+
const sourceRowId = `${credit.day}:${credit.ticketKey}`;
|
|
31
|
+
const markedByBlock = SourceConsumption.consumptionForBlocks(consumptionRows, sourceRowId, credit.blocks, report.sides, report.sourceEntries);
|
|
32
|
+
for (const [index, block] of credit.blocks.entries()) {
|
|
33
|
+
const key = evidenceBlockKey(sourceRowId, block);
|
|
34
|
+
const old = previous.get(key) ?? { clockify: 0, jira: 0 };
|
|
35
|
+
const marked = markedByBlock[index] ?? { clockify: 0, jira: 0 };
|
|
36
|
+
const ordinaryClockify = SourceConsumption.recordedSecondsInBlock(block, recorded?.intervals ?? [], "clockify");
|
|
37
|
+
const ordinaryJira = SourceConsumption.recordedSecondsInBlock(block, recorded?.intervals ?? [], "jira");
|
|
38
|
+
const jiraVerified = report.jiraAvailability === undefined || report.jiraAvailability === "verified";
|
|
39
|
+
const value = {
|
|
40
|
+
clockify: report.sides.clockify
|
|
41
|
+
? Math.min(block.seconds, marked.clockify + ordinaryClockify)
|
|
42
|
+
: old.clockify,
|
|
43
|
+
jira: report.sides.jira && jiraVerified
|
|
44
|
+
? Math.min(block.seconds, marked.jira + ordinaryJira)
|
|
45
|
+
: old.jira
|
|
46
|
+
};
|
|
47
|
+
if (value.clockify > 0 || value.jira > 0)
|
|
48
|
+
reconciled.set(key, value);
|
|
49
|
+
else
|
|
50
|
+
reconciled.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return reconciled;
|
|
54
|
+
};
|
|
55
|
+
/** The systems a plan was read under — what a confirmation writes to unless it says otherwise. */
|
|
56
|
+
export const planSides = (plan) => sidesOfScope(plan.plan.scope);
|
|
57
|
+
export const anyOwner = { checked: false, facts: new Map(), mode: "any", overrides: [] };
|
|
58
|
+
/**
|
|
59
|
+
* Whether a row may be proposed on this Issue Key.
|
|
60
|
+
*
|
|
61
|
+
* Four ways to be yours, and only one way not to be: the mode is `any`, the key is overridden, Jira
|
|
62
|
+
* says you are the assignee — or Jira was never asked. A row is withheld only on a positive answer
|
|
63
|
+
* that names somebody else, because withholding on silence is how hours disappear.
|
|
64
|
+
*/
|
|
65
|
+
export const isOwnedByMe = IssueFacts.isOwnedByMe;
|
|
66
|
+
const emptyRow = (ticketKey, day) => ({
|
|
67
|
+
clockifyDescription: null,
|
|
68
|
+
clockifySeconds: 0,
|
|
69
|
+
day,
|
|
70
|
+
intervals: [],
|
|
71
|
+
jiraSeconds: 0,
|
|
72
|
+
proposal: undefined,
|
|
73
|
+
ticketKey,
|
|
74
|
+
ticketTitle: null
|
|
75
|
+
});
|
|
76
|
+
/** Which systems a scope name puts in play. */
|
|
77
|
+
export const sidesOfScope = (scope) => ({
|
|
78
|
+
clockify: scope !== "jira",
|
|
79
|
+
jira: scope !== "clockify"
|
|
80
|
+
});
|
|
81
|
+
/** An absent key in the server-reconciled map means this block has no provider consumption. */
|
|
82
|
+
const consumedForBlock = (row, block, consumption) => consumption.get(evidenceBlockKey(row, block)) ?? { clockify: 0, jira: 0 };
|
|
83
|
+
const toWire = (draft, consumption) => ({
|
|
84
|
+
clockifyDescription: draft.clockifyDescription,
|
|
85
|
+
clockifySeconds: draft.clockifySeconds,
|
|
86
|
+
day: draft.day,
|
|
87
|
+
intervals: draft.intervals,
|
|
88
|
+
jiraSeconds: draft.jiraSeconds,
|
|
89
|
+
rowId: rowId(draft.ticketKey, draft.day),
|
|
90
|
+
ticketKey: draft.ticketKey,
|
|
91
|
+
ticketTitle: draft.ticketTitle,
|
|
92
|
+
proposal: draft.proposal === undefined ? undefined : {
|
|
93
|
+
activeSeconds: draft.proposal.activeSeconds,
|
|
94
|
+
blocks: draft.proposal.blocks.map((block) => ({
|
|
95
|
+
...(block.clockifyRefusal !== undefined && { clockifyRefusal: block.clockifyRefusal }),
|
|
96
|
+
consumed: consumedForBlock(rowId(draft.ticketKey, draft.day), block, consumption),
|
|
97
|
+
endMs: block.endMs,
|
|
98
|
+
seconds: block.seconds,
|
|
99
|
+
startMs: block.startMs,
|
|
100
|
+
...(block.sourceStartMs !== undefined && { sourceStartMs: block.sourceStartMs })
|
|
101
|
+
})),
|
|
102
|
+
clockifyDelta: draft.proposal.clockifyDelta,
|
|
103
|
+
confidence: draft.proposal.confidence,
|
|
104
|
+
jiraDelta: draft.proposal.jiraDelta,
|
|
105
|
+
// The credited evidence, which is the ceiling on an edited amount — not the gap, which is
|
|
106
|
+
// only what is missing right now and would fall to zero the moment one side is filled.
|
|
107
|
+
maxSeconds: draft.proposal.sessionSeconds,
|
|
108
|
+
sessionCount: draft.proposal.sessionIds.length,
|
|
109
|
+
signal: draft.proposal.signal
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
/** Reproject browser blocks when retention changes the authoritative consumed-time map. */
|
|
113
|
+
export const withProjectedConsumption = (held, consumption) => ({
|
|
114
|
+
...held,
|
|
115
|
+
consumption,
|
|
116
|
+
plan: {
|
|
117
|
+
...held.plan,
|
|
118
|
+
rows: held.plan.rows.map((row) => row.proposal === undefined
|
|
119
|
+
? row
|
|
120
|
+
: {
|
|
121
|
+
...row,
|
|
122
|
+
proposal: {
|
|
123
|
+
...row.proposal,
|
|
124
|
+
blocks: row.proposal.blocks.map((block) => ({
|
|
125
|
+
...block,
|
|
126
|
+
consumed: consumedForBlock(row.rowId, block, consumption)
|
|
127
|
+
}))
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Build the week the browser draws from one report.
|
|
134
|
+
*
|
|
135
|
+
* Rows are keyed by `(Issue Key, day)` and sorted by Issue Key then day, so a re-read after a write
|
|
136
|
+
* puts every row back where the reader last saw it.
|
|
137
|
+
*/
|
|
138
|
+
export const buildWeekPlan = (options) => {
|
|
139
|
+
const days = weekDays(options.monday);
|
|
140
|
+
// The report was read for exactly this week, so this only guards the two disagreeing. A row for
|
|
141
|
+
// another week would have no column to sit in, and dropping it silently is worse than not sending
|
|
142
|
+
// it at all.
|
|
143
|
+
const inWeek = new Set(days);
|
|
144
|
+
const drafts = new Map();
|
|
145
|
+
const draftFor = (ticketKey, day) => {
|
|
146
|
+
const id = rowId(ticketKey, day);
|
|
147
|
+
const existing = drafts.get(id);
|
|
148
|
+
if (existing !== undefined)
|
|
149
|
+
return existing;
|
|
150
|
+
const created = emptyRow(ticketKey, day);
|
|
151
|
+
drafts.set(id, created);
|
|
152
|
+
return created;
|
|
153
|
+
};
|
|
154
|
+
for (const recorded of options.report.recorded) {
|
|
155
|
+
if (!inWeek.has(recorded.day))
|
|
156
|
+
continue;
|
|
157
|
+
const draft = draftFor(recorded.ticketKey, recorded.day);
|
|
158
|
+
draft.clockifySeconds = recorded.clockifySeconds;
|
|
159
|
+
draft.jiraSeconds = recorded.jiraSeconds;
|
|
160
|
+
draft.clockifyDescription = recorded.clockifyDescription;
|
|
161
|
+
draft.intervals = recorded.intervals.map((interval) => {
|
|
162
|
+
const projected = { endMs: interval.endMs, source: interval.source, startMs: interval.startMs };
|
|
163
|
+
if (interval.entry === undefined)
|
|
164
|
+
return projected;
|
|
165
|
+
return {
|
|
166
|
+
...projected,
|
|
167
|
+
entry: { ...interval.entry, revision: options.entryRevision?.(interval.entry) ?? options.planId }
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const ownership = options.ownership ?? anyOwner;
|
|
172
|
+
const evidence = new Map();
|
|
173
|
+
const notMine = [];
|
|
174
|
+
for (const proposal of options.report.proposals) {
|
|
175
|
+
if (!inWeek.has(proposal.day))
|
|
176
|
+
continue;
|
|
177
|
+
// A ticket Jira says belongs to somebody else keeps its hours and loses its offer: no proposal
|
|
178
|
+
// on the row and no evidence held, so there is nothing for a confirmation to name either.
|
|
179
|
+
if (!isOwnedByMe(proposal.ticketKey, ownership)) {
|
|
180
|
+
notMine.push({
|
|
181
|
+
assignee: ownership.facts.get(proposal.ticketKey)?.assignee ?? null,
|
|
182
|
+
day: proposal.day,
|
|
183
|
+
seconds: proposal.sessionSeconds,
|
|
184
|
+
signal: proposal.signal,
|
|
185
|
+
ticketKey: proposal.ticketKey,
|
|
186
|
+
ticketTitle: ownership.facts.get(proposal.ticketKey)?.title ?? null
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const draft = draftFor(proposal.ticketKey, proposal.day);
|
|
191
|
+
draft.proposal = proposal;
|
|
192
|
+
const id = rowId(proposal.ticketKey, proposal.day);
|
|
193
|
+
evidence.set(id, { proposal, rowId: id });
|
|
194
|
+
}
|
|
195
|
+
// Titles last, so every row that exists gets one — including a row that only Clockify knows about.
|
|
196
|
+
for (const draft of drafts.values()) {
|
|
197
|
+
draft.ticketTitle = ownership.facts.get(draft.ticketKey)?.title ?? null;
|
|
198
|
+
}
|
|
199
|
+
const consumption = reconcileConsumption(options.report, options.consumption);
|
|
200
|
+
const rows = [...drafts.values()]
|
|
201
|
+
.sort((a, b) => (a.ticketKey === b.ticketKey ? a.day.localeCompare(b.day) : a.ticketKey.localeCompare(b.ticketKey)))
|
|
202
|
+
.map((draft) => toWire(draft, consumption));
|
|
203
|
+
return {
|
|
204
|
+
report: options.report,
|
|
205
|
+
boundScopes: {
|
|
206
|
+
clockify: options.report.sourceScopes?.clockify ?? null,
|
|
207
|
+
jira: options.report.sourceScopes?.jira ?? null
|
|
208
|
+
},
|
|
209
|
+
ownership,
|
|
210
|
+
createdAtMillis: options.createdAtMillis,
|
|
211
|
+
evidence,
|
|
212
|
+
consumption,
|
|
213
|
+
jiraReceiptIssueKeys: options.jiraReceiptIssueKeys ?? new Set(),
|
|
214
|
+
plan: {
|
|
215
|
+
attributorAvailable: options.report.attributorAvailable,
|
|
216
|
+
attributorCalls: options.report.attributorCalls,
|
|
217
|
+
days,
|
|
218
|
+
excludedDays: options.report.excludedDays
|
|
219
|
+
.filter((excluded) => inWeek.has(excluded.day))
|
|
220
|
+
.map((excluded) => ({ day: excluded.day, reason: excluded.reason })),
|
|
221
|
+
monday: days[0],
|
|
222
|
+
notMine: notMine.sort((a, b) => (a.day === b.day ? a.ticketKey.localeCompare(b.ticketKey) : a.day.localeCompare(b.day))),
|
|
223
|
+
ownership: ownership.mode,
|
|
224
|
+
ownershipChecked: ownership.checked,
|
|
225
|
+
planId: options.planId,
|
|
226
|
+
rows,
|
|
227
|
+
unlinkedClockify: options.report.unlinkedClockify.filter((entry) => inWeek.has(entry.day)).map(({ entry, ...slice }) => {
|
|
228
|
+
if (entry === undefined)
|
|
229
|
+
return slice;
|
|
230
|
+
return { ...slice, entry: { ...entry, revision: options.entryRevision?.(entry) ?? options.planId } };
|
|
231
|
+
}),
|
|
232
|
+
scope: options.scope,
|
|
233
|
+
sessionCount: options.report.sessionCount,
|
|
234
|
+
sessionScanAvailable: options.sessionScanAvailable ?? true,
|
|
235
|
+
sessionRootCount: options.report.sessionRootCount,
|
|
236
|
+
unattributed: options.report.unattributed
|
|
237
|
+
.filter((credit) => inWeek.has(credit.day))
|
|
238
|
+
.map((credit) => ({
|
|
239
|
+
cwds: [...credit.cwds],
|
|
240
|
+
day: credit.day,
|
|
241
|
+
seconds: credit.seconds,
|
|
242
|
+
sessionCount: credit.sessionCount
|
|
243
|
+
})),
|
|
244
|
+
withheld: options.report.withheld
|
|
245
|
+
.filter((credit) => inWeek.has(credit.day))
|
|
246
|
+
.map((credit) => ({
|
|
247
|
+
confidence: credit.confidence,
|
|
248
|
+
day: credit.day,
|
|
249
|
+
seconds: credit.seconds,
|
|
250
|
+
ticketKey: credit.ticketKey,
|
|
251
|
+
ticketTitle: ownership.facts.get(credit.ticketKey)?.title ?? null
|
|
252
|
+
}))
|
|
253
|
+
},
|
|
254
|
+
planId: options.planId
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
export { MINIMUM_WRITE_SECONDS, proposeWrite, selectedBlocks } from "../shared/writePlanning.js";
|
|
258
|
+
//# sourceMappingURL=WeekPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WeekPlan.js","sourceRoot":"","sources":["../../src/server/WeekPlan.ts"],"names":[],"mappings":"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"}
|