diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx
index 991cd12..da0ec2e 100644
--- a/src/components/FileUpload/FileUpload.tsx
+++ b/src/components/FileUpload/FileUpload.tsx
@@ -20,7 +20,7 @@ import {
usePatternRotationUpload,
usePatternValidation,
} from "@/hooks";
-import { useDisplayFilename } from "../../hooks/domain/useDisplayFilename";
+import { getDisplayFilename } from "../../utils/displayFilename";
import { PatternInfoSkeleton } from "../SkeletonLoader";
import { PatternInfo } from "../PatternInfo";
import { DocumentTextIcon } from "@heroicons/react/24/solid";
@@ -105,7 +105,7 @@ export function FileUpload() {
// Use prop pesData if available (from cached pattern), otherwise use local state
const pesData = pesDataProp || localPesData;
// Use currentFileName from App state, or local fileName, or resumeFileName for display
- const displayFileName = useDisplayFilename({
+ const displayFileName = getDisplayFilename({
currentFileName,
localFileName: fileName,
resumeFileName,
@@ -229,6 +229,7 @@ export function FileUpload() {
isConnected={isConnected}
isUploading={isUploading}
uploadProgress={uploadProgress}
+ boundsFits={boundsCheck.fits}
boundsError={boundsCheck.error}
onUpload={handleUpload}
patternUploaded={patternUploaded}
diff --git a/src/components/FileUpload/PyodideProgress.tsx b/src/components/FileUpload/PyodideProgress.tsx
index d6a6538..e8c3868 100644
--- a/src/components/FileUpload/PyodideProgress.tsx
+++ b/src/components/FileUpload/PyodideProgress.tsx
@@ -25,7 +25,7 @@ export function PyodideProgress({
- {isFileLoading && !pyodideReady
+ {isFileLoading
? "Please wait - initializing Python environment..."
: pyodideLoadingStep || "Initializing Python environment..."}
@@ -35,7 +35,7 @@ export function PyodideProgress({
- {isFileLoading && !pyodideReady
+ {isFileLoading
? "File dialog will open automatically when ready"
: "This only happens once on first use"}
diff --git a/src/components/FileUpload/UploadButton.tsx b/src/components/FileUpload/UploadButton.tsx
index f933729..22d7912 100644
--- a/src/components/FileUpload/UploadButton.tsx
+++ b/src/components/FileUpload/UploadButton.tsx
@@ -17,6 +17,7 @@ interface UploadButtonProps {
isConnected: boolean;
isUploading: boolean;
uploadProgress: number;
+ boundsFits: boolean;
boundsError: string | null;
onUpload: () => Promise
;
patternUploaded: boolean;
@@ -28,6 +29,7 @@ export function UploadButton({
isConnected,
isUploading,
uploadProgress,
+ boundsFits,
boundsError,
onUpload,
patternUploaded,
@@ -43,7 +45,7 @@ export function UploadButton({
return (
diff --git a/src/constants/workflowSteps.ts b/src/constants/workflowSteps.ts
index 63b7864..264ccea 100644
--- a/src/constants/workflowSteps.ts
+++ b/src/constants/workflowSteps.ts
@@ -3,9 +3,9 @@
*/
export interface WorkflowStep {
- id: number;
- label: string;
- description: string;
+ readonly id: number;
+ readonly label: string;
+ readonly description: string;
}
export const WORKFLOW_STEPS: readonly WorkflowStep[] = [
diff --git a/src/hooks/domain/useDisplayFilename.ts b/src/utils/displayFilename.ts
similarity index 84%
rename from src/hooks/domain/useDisplayFilename.ts
rename to src/utils/displayFilename.ts
index f239ed2..39cae3c 100644
--- a/src/hooks/domain/useDisplayFilename.ts
+++ b/src/utils/displayFilename.ts
@@ -1,5 +1,5 @@
/**
- * useDisplayFilename Hook
+ * getDisplayFilename Utility
*
* Determines which filename to display based on priority:
* 1. currentFileName (from pattern store)
@@ -8,7 +8,7 @@
* 4. Empty string
*/
-export function useDisplayFilename(options: {
+export function getDisplayFilename(options: {
currentFileName: string | null;
localFileName: string;
resumeFileName: string | null;
diff --git a/src/utils/threadMetadata.ts b/src/utils/threadMetadata.ts
index 5433b10..0854573 100644
--- a/src/utils/threadMetadata.ts
+++ b/src/utils/threadMetadata.ts
@@ -1,6 +1,54 @@
/**
- * Format thread metadata for display
+ * Format thread metadata for display.
+ *
* Combines brand, catalog number, chart, and description into a readable string
+ * using the following rules:
+ *
+ * - The primary part consists of the brand and catalog number:
+ * - The brand (if present) appears first.
+ * - The catalog number (if present) is prefixed with `#` and appended after
+ * the brand, separated by a single space (e.g. `"DMC #310"`).
+ * - The secondary part consists of the chart and description:
+ * - The chart is omitted if it is `null`/empty or exactly equal to
+ * `threadCatalogNumber`.
+ * - The chart (when shown) and the description are joined with a single
+ * space (e.g. `"Anchor 24-colour Black"`).
+ * - The primary and secondary parts are joined with `" • "` (space, bullet,
+ * space). If either part is empty, only the non-empty part is returned.
+ *
+ * Examples:
+ *
+ * - Brand and catalog only:
+ * - Input:
+ * - `threadBrand: "DMC"`
+ * - `threadCatalogNumber: "310"`
+ * - `threadChart: null`
+ * - `threadDescription: null`
+ * - Output: `"DMC #310"`
+ *
+ * - Brand, catalog, and description:
+ * - Input:
+ * - `threadBrand: "DMC"`
+ * - `threadCatalogNumber: "310"`
+ * - `threadChart: null`
+ * - `threadDescription: "Black"`
+ * - Output: `"DMC #310 • Black"`
+ *
+ * - Brand, catalog, chart (different from catalog), and description:
+ * - Input:
+ * - `threadBrand: "Anchor"`
+ * - `threadCatalogNumber: "403"`
+ * - `threadChart: "24-colour"`
+ * - `threadDescription: "Black"`
+ * - Output: `"Anchor #403 • 24-colour Black"`
+ *
+ * - Chart equal to catalog number (chart omitted):
+ * - Input:
+ * - `threadBrand: "DMC"`
+ * - `threadCatalogNumber: "310"`
+ * - `threadChart: "310"`
+ * - `threadDescription: "Black"`
+ * - Output: `"DMC #310 • Black"`
*/
interface ThreadMetadata {