15 Commits
Author SHA1 Message Date
Dylan M. Taylor 4ecd4b7eea Add manual verified updates and bump ArtCraft to 0.42.1 (#2835)
ArtCraft’s Help menu has news but no application update check, and its
Flatpak cannot invoke the host updater. Add **Help → Check for
Updates…** with background checks, current/available versions, explicit
Update and restart, progress and failure states, and Releases fallback.
Browser users are offered a reload and the desktop releases.

Use the same verified `craft-update` core as the native Craft apps,
pinned to the immutable reviewed commit in
https://github.com/storytold/craft-libs/pull/970. Keep ArtCraft’s
existing `ai.artcraft.app` identity and release naming. Honor launcher
pins, reject active generation tasks before download and again before
replacement, preserve the native window during installation, defer the
helper until accepted exit, and report helper outcomes on startup.
Registered NSIS copies use Releases; they are not overwritten as
portable folders.

Grant the host update interface and read-only launcher choices in the
existing Flatpak manifest. No frontend network-policy expansion. Add a
separate version commit changing the canonical Tauri configuration and
display version to **0.42.1**, with a regression keeping them
synchronized. Once this and the shared-library prerequisite are green
and merged, merge main into the release branch, finish all platform
drafts, verify signed checksums/assets, and publish.

Validation: native all-target cargo check inside GNOME SDK 51; all six
focused Rust updater tests; all ten Help-menu/UI tests; TypeScript
project build; production frontend build; Flatpak permission regression
red before and green after. All six native SDK tests also pass at the
final 0.42.1 version. The existing lockfile needs compatible chrono and
crc32fast updates for the shared updater; other dependency revisions are
preserved.
2026-11-11 21:36:49 -05:00
Dylan M. Taylor 55e6e5cabb frontend: Sonar follow-ups (S8786, S6852, S7059) (#2824)
Two issues SonarCloud raised on main after #2821/#2822 merged:

- **typescript:S8786** `SceneState.ts` `base64ToFile`: #2821's
`/:([^;]*);/` is still flagged. The mime type is now read with
`indexOf`: the text between the first `:` and the `;` after it, else
`image/png`. Checked equal to the regex on `data:image/png;base64`,
`data:;base64`, a header with no `:` or `;`, and several `:`.
- **typescript:S6852** `MentionTextarea`: the `role="textbox"` editor
gets `tabIndex={disabled ? -1 : 0}`. A `contentEditable` element was
focusable already, so nothing changes for users.

Typechecked locally against main (no new errors).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved image import handling so MIME types are identified more
reliably, with PNG used as a fallback when the type cannot be
determined.
* Disabled text editors are no longer reachable through sequential
keyboard navigation; enabled editors remain tabbable.
* Physics support is initialized during editor setup after the required
canvas and container elements are available.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
- **typescript:S7059** `editor.ts`: `ensureAmmoLoaded()` (an async load)
moves out of the `Editor` constructor to `EngineProvider`, the only
place an `Editor` is built, right before `new Editor(...)`. The loader
is a singleton, so the script is still appended once.
2026-11-10 22:51:32 -05:00
Dylan M. Taylor 7c8419fabd frontend: Number.parseFloat/parseInt/isFinite/isNaN (Sonar S7773) (#2819)
Clears the 82 open typescript:S7773 issues (35 files), only on the lines
Sonar flagged.

- `parseFloat` / `parseInt` → `Number.parseFloat` / `Number.parseInt`:
the same functions (`Number.parseFloat === parseFloat`).
- `isFinite` / `isNaN` → `Number.isFinite` / `Number.isNaN`: they differ
only for arguments that aren't numbers. TypeScript's lib types the
globals as taking `number`, and each call already passes one (`maxLen`,
`Number(value)`, a parsed count), so nothing changes. Typecheck is left
to CI.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Improvements**
* Video frame extraction and watermark-removal pages now provide
consistent playback controls, including seeking, playback, mute, and
volume adjustment.
* Prompt-length indicators and validation consistently reflect
configured limits, including unlimited limits.
* **New Features**
* Prompt fields display a character counter that highlights text
exceeding the configured limit.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Also: rebased on main, and the length counter and over-limit check that
the five prompt boxes repeated are now `PromptLengthCounter` /
`isOverPromptLimit` (`promptbox/src/lib/PromptLengthCounter.tsx`). That
removes the duplication the new-code gate counted against this PR.
2026-11-10 22:40:36 -05:00
FredAppsandDylan M. Taylor 130bfd71d6 libs/components: mark fire-and-forget promises with void (Sonar S9383) (#2807)
## Summary

Resolves all 66 SonarCloud **typescript:S9383** findings ("Promises must
be awaited, end with a call to .catch, end with a call to .then with a
rejection handler or be explicitly marked as ignored with the `void`
operator") in `frontend/libs/components`, across 41 files:

| Library                | Findings |
|------------------------|----------|
| `upload-modal`         | 14       |
| `promptbox`            | 11       |
| `settings-modal`       | 11       |
| `pagescene`            | 10       |
| `video-editor`         | 7        |
| `lightbox-modal`       | 3        |
| `pagedraw`             | 3        |
| `generation-list`      | 2        |
| `help-menu`            | 2        |
| `gallery-modal`        | 1        |
| `pricing-modal`        | 1        |
| `provider-setup-modal` | 1        |

The flagged expressions include settings and session loaders
(`fetchData()`, `fetchSession()`) called from effects, upload starts and
retries in the upload modals, enqueue handlers in the prompt boxes,
`audio.play()` and `el.play()` previews, scene render and load calls in
`pagescene`, bookmark actions in `video-editor`, and async IIFEs.

This is part 3 of 3 for artcraft's 157 S9383 findings. Part 1 (Tauri
libraries) is #2805, and part 2 (`apps/artcraft/app`) is #2806.

Some of these files are also changed by open PRs for unrelated reasons,
for example #2615 (settings panes), #2519, #2520 and #2083. Each change
here inserts one word at the start of a statement, so a rebase either
way is trivial.

## Approach

This ports the approach of storytold/artcraft-services#179, which fixes
the same rule in artcraft-services. **#179 is still open and unmerged.**
This PR follows its approach and does not depend on it.

- Each flagged expression is a fire-and-forget promise: a listener
`setup()`, an `unlisten.then(f => f())` cleanup, a loader called from an
effect, an upload kicked off from a handler, or an async IIFE. Prefixing
it with `void` marks the ignored promise explicitly, which is the fix
the rule documents.
- It deliberately does not add `.catch()` everywhere. These call sites
already handle failures inside the async function or surface them
through their own state, and empty handlers would hide errors.
- Async IIFEs become `void (async () => …)()`. `if (cond) call()`
becomes `if (cond) void call()`.
- There is no behaviour change. `void` only discards the promise value,
which was already being discarded.

Each insertion is placed at the exact `textRange` start offset of the
SonarCloud finding, with no other edits.

## Testing

- Each changed file parses with `esbuild <file>`, using the same check
as #179.
- I parsed each changed file with the TypeScript 5.9 compiler API
(`createSourceFile`). That found no parse errors, and every inserted
`void` is either a whole expression statement or an arrow-function body
applied to a call, never part of a larger expression.
- `frontend/.eslintrc.json` has no `no-void` rule.
- I did not run the full Nx type-check, lint or test targets, because
`frontend/node_modules` is not installed in my environment. Please rely
on CI.
- No tests are added. Discarding an already-discarded promise value is
not observable, so no test can fail before this change and pass after
it.

**Added by @dylanmtaylor, to clear the new-code duplication gate.** The
`void` lines sat inside copy-pasted code:
- `ProviderAccountBlock` replaces the identical `GrokAccountBlock` and
`MidjourneyAccountBlock`.
- `UploadFilesPanels` (file list, resize handle, previous/next; each
modal passes its preview) replaces the layout the 3D, Image and Splat
upload modals repeated.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Multi-file uploads across image, 3D, and splat workflows now share a
two-panel layout with file statuses, preview selection, removal, retry,
and previous/next navigation.
* Grok and Midjourney account controls now use a shared interface that
displays login status and provides available connect or disconnect
actions. Failed credential checks clear the displayed session, and
account controls show a loading state while checking.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-11-10 22:39:17 -05:00
Dylan M. Taylor d6772fdd94 frontend: uuidv4 for local ids instead of Math.random (Sonar S2245) (#2820)
Clears the 38 open typescript:S2245 issues (18 files).

Each `Math.random().toString(36)…` made a local id: an upload row, a
reference title, a batch or subscriber id, an idempotency token, an
image cache-buster. They now use `uuidv4()` from `uuid`, which is
already a dependency and imported the same way elsewhere (`import { v4
as uuidv4 } from "uuid"`). It reads `crypto.getRandomValues`, so it also
works where `crypto.randomUUID` doesn't (outside a secure context). The
`crypto.randomUUID ? … : Math.random()…` fallbacks therefore collapse to
`uuidv4()`, and `subscriberId ? subscriberId : …` becomes `subscriberId
|| uuidv4()`.

Ids are now 36 characters instead of 5–13. Nothing parses or compares
their length. Typecheck is left to CI.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Improvements**
* Creative tools now handle identifiers more consistently for generated
content, uploads, reference media, and generation batches.
* Completed generation results are displayed more reliably when there
isn’t a matching pending result.
* These improvements span image, video, audio, 3D, drawing, and
visual-effects workflows, helping keep related media and results
organized across the app.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Also: `ImageTo3DStore` and `ImageTo3DWorldStore` shared the same 30-line
`completeGeneration` (find the subscriber's result, else the first
pending one, and mark it completed, or add a completed one). It's now
one `completeGenerationResult` in `pages/Stores/`, which removes the
duplication the new-code gate counted against this PR.
2026-11-10 21:49:49 -05:00
Dylan M. Taylor 8d6fc363b1 frontend: roles, keyboard access and label association (Sonar S6848, S6853) (#2822)
Clears the 59 open typescript:S6848 issues ("non-native interactive
element without a role").

- **Clickable things that act like buttons (16)**: upload and preview
tiles, storyboard frames, help items, reference thumbnails, remove
buttons, deck cards, asset items, popover menu items. They get
`role="button"`, `tabIndex={0}` and `onKeyDown={clickOnEnterOrSpace}`,
the pattern `GalleryRow` already uses. `clickOnEnterOrSpace` (new,
`@storyteller/common`) calls the element's own `click()`, so each
`onClick` runs unchanged. Menu items also get `aria-disabled` when
disabled (their `onClick` already ignores the click). The bulk-select
box is `role="checkbox"` with `aria-checked`. In `ImagePromptRow` the
attributes come before dnd-kit's `attributes`/`listeners` spread, so
when reordering is on, dnd-kit's own role and keyboard sensor still win.
- **Not controls (42)**: modal and menu backdrops, wrappers that only
stop event propagation, mouse-only drag/resize handles and slider
tracks, trigger wrappers around a child button, the tooltip body. They
get `role="presentation"`, which says what they already are to assistive
tech. Nothing changes for mouse users.
- **The prompt editor** (`contentEditable` in `MentionTextarea`) is
`role="textbox"` with `aria-multiline="true"`.

Mouse behaviour is unchanged. Keyboard users can now Tab to and activate
the 16 button-like elements. Typecheck is left to CI.

**typescript:S6853 (13)**: labels tied to their controls. In the
storyboard metadata editor, the signup form and the cost modal, each
label gets `htmlFor` and its control a matching `id` (`Input` and
`Select` pass `id` through, and `Select`'s goes on its `ListboxButton`).
These render once per page. The two colour-picker labels, which already
wrap their `<input type="color">`, get `sr-only` text in place of their
`title`-only name.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Accessibility**
* Added keyboard access to image tiles, asset cards, list items, and
other controls, including Enter and Space activation.
* Improved screen-reader support with clearer labels for controls and
thumbnails, and associations between form labels and fields.
* Updated accessibility semantics for decorative content and interactive
elements.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-10 20:22:41 -05:00
Dylan M. Taylor f9f4c99e4f frontend: Sonar reliability fixes (promises in conditions, async executors, identical branches, reduce, regex) (#2821)
Clears 51 open TypeScript issues. Typecheck and build are left to CI.

**typescript:S6544 (17)**
- A cached promise checked by truthiness (`if (existing)`, `if
(cached)`, `if (ammoLoadPromise)`…) is checked as `!== undefined` / `!==
null` against its map or field type. Same branches, since a Promise is
always truthy.
- `scene.ts` `load_glb_wrapped_no_cors` / `load_splat_wrapped_no_cors`:
the fetch moves out of the `async` Promise executor. The methods were
already `async`, so a failed fetch now `throw`s (rejects the same way),
and only the callback APIs (`GLTFLoader.parse`, `SplatMesh` `onLoad`)
stay inside `new Promise`.
- `SceneManagerAPI` declares the promises its implementation returns
(`create`/`retrieve` → `Promise<THREE.Object3D | undefined>`,
`update`/`delete` → `Promise<void>`). Nothing calls through the
interface.
- `convertFbxToGlb.worker.ts`: `onmessage` calls `void convert(event)`.

**typescript:S3923 / S1764 / S3516 / S905 (12)**: identical branches
collapsed (`variant === "world" ? "max-w-[1600px]" : "max-w-[1600px]"`,
`ScissorIcon : ScissorIcon`, `index % 2 === 0 ? point + offset : point +
offset`, PromptBox3D's two `setIsEnqueueing(false); return;` arms,
`{DEFAULT_GRID_CONFIG ? null : null}` and its now-unused import), `x &&
x`, the `this.gridHelper;` statement, and `CurrentUserBadge`, which
always rendered `null` (its markup was commented out).

**typescript:S6959 (5)**: each `reduce` starts from the first element.
Every one is behind an emptiness check, and comparing the first element
with itself keeps it.

**typescript:S2871 (4)**: `sort((a, b) => a.localeCompare(b))` for the
order-independent cache keys and the board and font name lists.

**typescript:S8786 (8)**
- Markdown link text is `[^\][]+`. `@image(url, size)` drops the `\s*`
before the size (it's `.trim()`ed already). ATX headings `trimStart()`
the text instead of `\s*`. Slugs trim one `-` (runs were already
collapsed to one).
- `color.ts` `stripCssNoise`: `!important` is replaced by a space, then
trailing `;` are trimmed in a loop.
- Data-URL mime: `/:([^;]*);/`.
- `ControlPanelSceneObject` number check: `/^-?\d*(\.\d*)?$/`. The old
`.` was unescaped and matched any character, so `12a3` passed as a
number.

**Others**: `window.open(…, "noopener,noreferrer")` (S5148).
`AlertTitle` renders `children` explicitly (S6850). The folder name hash
wraps with `Math.imul(…, 1)` instead of `| 0`, the same int32 result
(S7767). `autoFocus` is removed from the two rename inputs, which their
refs already focus and select (S9379).

**Not here**: S7059 in `editor.ts` (`ensureAmmoLoaded()` in the
constructor) is on a line #2807 changes.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability when loading 3D scene assets and completing
drag-and-drop operations.
* Corrected numeric input validation, scene import handling, and video
duration tie handling.
* Improved media, audio, and video cache handling to avoid unnecessary
retries or missed waits.
* Refined markdown parsing for links, headings, image directives, and
heading anchors.
  * Discord invites now open with additional browser protections.
  * Improved CSS color cleanup and handling of pasted scene elements.
* **Improvements**
* Made ordering of tags, fonts, and prompt-related items more
consistent.
  * Improved video editor timeline selections.
* Removed the non-functional account badge from the video editor header.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

**typescript:S1848 (2)**: `new SplatMesh({ …, onLoad })` created an
instance only for its callback. `scene.ts` now awaits `mesh.initialized`
(Spark's promise, which resolves after load), so a failed load rejects
instead of leaving the promise pending. `viewer-3d.tsx` runs the same
body in `initialized.then`, with a `.catch` that logs.

Kept as is: `TabState.setActiveTab` still resolves `true` (S3516 stays
open), because `TabState.spec.ts` and the tab-change benchmark read its
result.
2026-11-10 20:22:36 -05:00
3e5793b693 PageScene: dispose GPU assets on desktop (#1972)
Targets PageScene and other 3D viewers, handles uniforms and textures,
amongst other general editor mount improvements

---------

Co-authored-by: heojunfo <kleague3@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 17:56:14 -07:00
BFlat 90c7a8a118 Match desktop app styling to the webapp (#1963) 2026-09-28 12:29:54 +07:00
Brandon Thomas 1b27c31531 thumbnail speedup (and fix for non-rendering thumbnails) (#1964) 2026-09-28 01:28:45 -04:00
Brandon Thomas 780e4494e9 fix login video on desktop (#1958) 2026-09-26 17:08:31 -07:00
Brandon Thomas 8456e2fd06 website based login challenges (#1956) 2026-09-26 03:42:09 -07:00
Brandon Thomas 3c5038c159 Added new models and modes: GPT Image 2.5, Wan, Output Formats (#1954) 2026-09-18 16:23:44 -04:00
Brandon Thomas 584fdb7044 backport midjourney fixes from ArtCraft-X (#1953) 2026-09-14 12:45:24 -04:00
Brandon Thomas 0004726a36 ArtCraft Desktop (fresh start)
This is a fresh start for the `artcraft` repo.

The old monorepo, which included our web API gateway, job services, SRE
tools, various frontends, and more has moved to `artcraft-services`

You can find the full git history here:

https://github.com/storytold/artcraft-services

This change was motivated by the fact that the entire git history was
hundreds of gigabytes in size, which is unwieldy for anyone not in the
core team that doesn't care about support services outside of the core
artcraft app.

We wanted to keep this repo in its well-known place since we already
have users and activity here.

Please update your remotes to either `artcraft-services` or make a fresh
clone of this repo.

History can be found at the artcraft-services repo.
2026-09-11 14:19:51 -04:00