Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ResultAsync } from "neverthrow";
import { getErrorMessage } from "../../../../../utils/get-error-message";
import type { DictEntry } from "../../../domain/dict-note/types";
import { toApiCommandError } from "../../../errors";
import { cssSuffixFor } from "../../../targets/de/sections/section-css-kind";
import {
DictSectionKind,
TitleReprFor,
} from "../../../targets/de/sections/section-kind";
import type { CommandError } from "../../types";
import { CommandErrorKind } from "../../types";
import {
buildEntityMeta,
buildLinguisticUnitMeta,
Expand Down Expand Up @@ -164,10 +163,7 @@ export function generateSections(
if (ctx.matchedEntry) {
return ResultAsync.fromPromise(
buildReEncounterResult(ctx),
(error): CommandError => ({
kind: CommandErrorKind.ApiError,
reason: getErrorMessage(error),
}),
toApiCommandError,
);
}

Expand Down Expand Up @@ -263,9 +259,6 @@ export function generateSections(
targetBlockId: generated.entryId,
};
})(),
(error): CommandError => ({
kind: CommandErrorKind.ApiError,
reason: getErrorMessage(error),
}),
toApiCommandError,
);
}
16 changes: 16 additions & 0 deletions src/commanders/textfresser/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import z from "zod";
import { getErrorMessage } from "../../utils/get-error-message";
import {
BASE_COMMAND_ERROR_KIND_STR,
type BaseCommandError,
Expand Down Expand Up @@ -44,3 +45,18 @@ export const AttestationParsingErrorKind =
export type AttestationParsingError =
| { kind: typeof AttestationParsingErrorKind.WikilinkNotFound }
| { kind: typeof AttestationParsingErrorKind.BlockIdNotFound };

// ─── Error Helpers ───

/** Extract a human-readable reason from a CommandError. */
export function extractErrorReason(error: CommandError): string {
return "reason" in error ? error.reason : `Command failed: ${error.kind}`;
}

/** Convert an unknown thrown value into an ApiError CommandError. */
export function toApiCommandError(error: unknown): CommandError {
return {
kind: CommandErrorKind.ApiError,
reason: getErrorMessage(error),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { sleep } from "../../../../utils/sleep";
import type { LemmaResult } from "../../commands/lemma/types";
import type { CommandError, CommandInput } from "../../commands/types";
import { buildPolicyDestinationPath } from "../../common/lemma-link-routing";
import { extractErrorReason } from "../../errors";
import type {
InFlightGenerate,
PendingGenerate,
Expand Down Expand Up @@ -169,10 +170,7 @@ export function createBackgroundGenerateCoordinator(params: {
if (generateResult.isErr()) {
const cleanupSummary = await cleanupIfEmpty();
const error = generateResult.error;
const reason =
"reason" in error
? error.reason
: `Command failed: ${error.kind}`;
const reason = extractErrorReason(error);
throw new Error(
`${reason} (cleanup=${cleanupSummary}, owned=${targetOwnedByInvocation}, existedBefore=${targetExistedBefore})`,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { errAsync, ResultAsync } from "neverthrow";
import type { CommandContext } from "../../../../managers/obsidian/command-executor";
import type { VaultActionManager } from "../../../../managers/obsidian/vault-action-manager";
import { logger } from "../../../../utils/logger";
import { resolveAttestation } from "../../commands/lemma/lemma-command";
import type { CommandError, CommandInput } from "../../commands/types";
import { buildPolicyDestinationPath } from "../../common/lemma-link-routing";
import { CommandErrorKind } from "../../errors";
import type { TextfresserState } from "../../state/textfresser-state";
import { notifyAndLogError } from "../shared/notify-error";
import {
buildLemmaInvocationKey,
getValidLemmaInvocationCache,
Expand Down Expand Up @@ -49,15 +49,7 @@ export function executeLemmaFlow(params: {
readContent: (splitPath) => vam.readContent(splitPath),
state,
}),
).mapErr((error) => {
const reason =
"reason" in error
? error.reason
: `Command failed: ${error.kind}`;
notify(`⚠ ${reason}`);
logger.warn("[Textfresser.Lemma] Failed:", error);
return error;
});
).mapErr(notifyAndLogError(notify, "Textfresser.Lemma"));
}

return new ResultAsync(
Expand Down Expand Up @@ -99,13 +91,5 @@ export function executeLemmaFlow(params: {
notify(`✓ ${lemma.lemma}${pos}`);
requestBackgroundGenerate(notify);
})
.mapErr((error) => {
const reason =
"reason" in error
? error.reason
: `Command failed: ${error.kind}`;
notify(`⚠ ${reason}`);
logger.warn("[Textfresser.Lemma] Failed:", error);
return error;
});
.mapErr(notifyAndLogError(notify, "Textfresser.Lemma"));
}
16 changes: 16 additions & 0 deletions src/commanders/textfresser/orchestration/shared/notify-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { logger } from "../../../../utils/logger";
import type { CommandError } from "../../commands/types";
import { extractErrorReason } from "../../errors";

/** Returns a .mapErr() callback that notifies the user and logs the error. */
export function notifyAndLogError(
notify: (message: string) => void,
logContext: string,
): (error: CommandError) => CommandError {
return (error) => {
const reason = extractErrorReason(error);
notify(`⚠ ${reason}`);
logger.warn(`[${logContext}] Failed:`, error);
return error;
};
}
12 changes: 2 additions & 10 deletions src/commanders/textfresser/textfresser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import type { EventHandler } from "../../managers/obsidian/user-event-intercepto
import type { VaultActionManager } from "../../managers/obsidian/vault-action-manager";
import type { ApiService } from "../../stateless-helpers/api-service";
import type { LanguagesConfig } from "../../types";
import { logger } from "../../utils/logger";
import { actionCommandFnForCommandKind } from "./commands";
import type { CommandInput, TextfresserCommandKind } from "./commands/types";
import type { PathLookupFn } from "./common/target-path-resolver";
Expand All @@ -21,6 +20,7 @@ import {
import { createWikilinkClickHandler } from "./orchestration/handlers/wikilink-click-handler";
import { executeLemmaFlow } from "./orchestration/lemma/execute-lemma-flow";
import { dispatchActions } from "./orchestration/shared/dispatch-actions";
import { notifyAndLogError } from "./orchestration/shared/notify-error";
import {
createInitialTextfresserState,
type TextfresserState,
Expand Down Expand Up @@ -104,15 +104,7 @@ export class Textfresser {
this.scrollToTargetBlock();
}
})
.mapErr((error) => {
const reason =
"reason" in error
? error.reason
: `Command failed: ${error.kind}`;
notify(`⚠ ${reason}`);
logger.warn(`[Textfresser.${commandName}] Failed:`, error);
return error;
});
.mapErr(notifyAndLogError(notify, `Textfresser.${commandName}`));
}

createHandler(): EventHandler<WikilinkClickPayload> {
Expand Down