All files / web/src/components/create/abacus PrintSubmitErrorNotice.stories.tsx

0% Statements 0/217
0% Branches 0/1
0% Functions 0/1
0% Lines 0/217

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218                                                                                                                                                                                                                                                                                                                                                                                                                                                   
// Abacus Studio — PrintSubmitErrorNotice stories (gh#163).
//
// Every failure the submit panel can show, staged without a live print service.
// Each story feeds a real service envelope through `describeSubmitFailure` — the
// same function the panel uses — so the copy shown here is exactly the copy a
// user gets, and it can't drift from the mapping. The headline example that
// motivated this work is `PrinterBusyHeldJob`: the prod 409 that used to read
// "Submit failed (409). Try again."

import type { Meta, StoryObj } from '@storybook/react'
import { PrintSubmitErrorNotice } from './PrintSubmitErrorNotice'
import type { JobRow } from './print-jobs'
import { describeSubmitFailure } from './print-submit-failure'

const meta: Meta<typeof PrintSubmitErrorNotice> = {
  title: 'AbacusStudio/PrintSubmitErrorNotice',
  component: PrintSubmitErrorNotice,
  tags: ['autodocs'],
  decorators: [
    (Story) => (
      // the studio's dark sidebar column; the notice's palette assumes it
      <div
        style={{
          width: 300,
          padding: 12,
          borderRadius: 12,
          background: '#111827',
          fontFamily: 'ui-sans-serif, system-ui, sans-serif',
          fontSize: 12,
        }}
      >
        <Story />
      </div>
    ),
  ],
}
export default meta

type Story = StoryObj<typeof PrintSubmitErrorNotice>

const resolveDefaults = {
  attention: [],
  notices: [],
  startPolicy: null,
  acknowledged: [],
  updatedAt: null,
}
const heldJob: JobRow = {
  id: 'j0',
  name: 'Abacus — 4 columns',
  phase: 'ready',
  progress: null,
  error: null,
  ...resolveDefaults,
}
const printingJob: JobRow = {
  id: 'j1',
  name: 'Abacus — 7 columns',
  phase: 'printing',
  progress: 42,
  error: null,
  ...resolveDefaults,
}

/**
 * The prod 409 that motivated this work. THH refuses the submit because the
 * printer already holds a job — here the "ready" (held) one from a prior
 * submit — and the notice names it and says exactly what to do, in place of the
 * old "Submit failed (409). Try again."
 */
export const PrinterBusyHeldJob: Story = {
  args: {
    failure: describeSubmitFailure(409, {
      detail: { code: 'printer_busy', message: 'busy', activeJob: { jobId: 'j0' } },
    }),
    blockingJob: heldJob,
    fallbackMessage: 'Submit failed.',
  },
}

/** Same refusal while the blocking job is actively printing — the phase and
 *  progress read straight off the roster row. */
export const PrinterBusyPrinting: Story = {
  args: {
    failure: describeSubmitFailure(409, {
      detail: { code: 'printer_busy', activeJob: { jobId: 'j1' } },
    }),
    blockingJob: printingJob,
    fallbackMessage: 'Submit failed.',
  },
}

/** printer_busy where the service named no job (or it isn't in our roster):
 *  still an honest explanation and the same remediation, no phantom job line. */
export const PrinterBusyUnnamed: Story = {
  args: {
    failure: describeSubmitFailure(409, { detail: { code: 'printer_busy' } }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** A per-key ticket rejection — the headline points at the settings editor,
 *  where the highlighted keys live. */
export const InvalidTicket: Story = {
  args: {
    failure: describeSubmitFailure(400, {
      detail: {
        code: 'invalid_ticket',
        message: 'bad',
        keys: { layer_height: 'out of range' },
      },
    }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** The printer needs something confirmed before it can start — the missing
 *  acknowledgements are named in the remediation. */
export const AcknowledgementRequired: Story = {
  args: {
    failure: describeSubmitFailure(409, {
      detail: {
        code: 'acknowledgement_required',
        message: 'Confirm the bed and AMS mapping before printing.',
        missing: ['bed_temp_unverified', 'ams_mapping'],
      },
    }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** A code we don't special-case: the service's own message is shown VERBATIM
 *  rather than swallowed into a status number. */
export const UnknownCodedRefusal: Story = {
  args: {
    failure: describeSubmitFailure(409, {
      detail: {
        code: 'nozzle_mismatch',
        message: 'This printer has a 0.4 mm nozzle; this job needs 0.2 mm.',
      },
    }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** A 409 with no parseable body — still named a conflict, never a blind retry. */
export const ConflictNoBody: Story = {
  args: {
    failure: describeSubmitFailure(409, null),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** A 5xx / unreachable: the one case where "try again" is the honest advice. */
export const TransientServiceError: Story = {
  args: {
    failure: describeSubmitFailure(503, null),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** Credentials rejected — a re-pair, not a retry. */
export const CredentialsRejected: Story = {
  args: {
    failure: describeSubmitFailure(403, null),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** A throw that isn't a coded submit rejection (e.g. the pre-submit export
 *  render timing out): `failure` is null and the raw Error message is shown. */
export const NonCodedThrow: Story = {
  args: {
    failure: null,
    blockingJob: null,
    fallbackMessage: "The 3D render didn't finish — try again",
  },
}

// ── Two-stage feet print (Gitea #38 / things-haunt-house #456) ──────────────

/** Stage B can no longer chain: something else touched the printer's plate
 *  since Stage A completed, or Stage A isn't the last job any more. */
export const InvalidChain: Story = {
  args: {
    failure: describeSubmitFailure(400, {
      detail: {
        code: 'invalid_chain',
        message: 'job printapi-1788813519429-8c69346d is not the last job on x1c',
      },
    }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}

/** The split's external feed isn't the same family as the AMS tray the feet
 *  print from — the gateway refuses the plan before slicing. */
export const InvalidFilaments: Story = {
  args: {
    failure: describeSubmitFailure(400, {
      detail: {
        code: 'invalid_filaments',
        message: 'split.feed family PLA does not match filament 0 (TPU)',
      },
    }),
    blockingJob: null,
    fallbackMessage: 'Submit failed.',
  },
}