Quellcode durchsuchen

fix(slicer): preset visibility + lookup precedence + signed-out banner + AMS slot badges (#1712)

  Four #1712 issues from the 2026-06-04 Orca Cloud integration:

  (1) Tier order put Orca Cloud above everything across SliceModal,
  auto-pick scoring, dropdown groups, the AMS slot picker, and the
  backend precedence. Bambu-Cloud-only users saw their profiles
  deprioritised behind an empty Orca tier.

  (2) Cross-tier dedup hid a same-named preset in all but the highest-
  priority tier. A user with both a local-imported and an Orca-synced
  "Bambu PLA Basic" couldn't see the Orca copy as a picker option.

  (3) CloudStatusBanner nagged signed-out users with a permanent
  "Sign in to Orca Cloud" line at the top of every slice -- even after
  explicit logout. Bambu Cloud had the symmetric problem.

  (4) ConfigureAmsSlotModal source badges were inconsistent: Orca rows
  showed only "Custom" (no source identity), Bambu Cloud built-in rows
  had no badge at all, and the orthogonal isUser-driven "Custom" badge
  collided with the source badge for cloud user presets.

  Order is local > orca_cloud > cloud > standard everywhere it lives
  (SliceModal SLICE_MODAL_TIER_ORDER + TIER_BONUS + dropdown tier list,
  ConfigureAmsSlotModal sourceOrder, and backend precedence). The order
  drives auto-pick + visual group rendering; it does NOT hide profiles.

  _dedupe_by_name replaced with _enrich_cloud_metadata: every tier
  returns its full list across all three slots (printer / process /
  filament). The function still backfills Bambu Cloud filament metadata
  from same-named local / orca_cloud / standard entries so cloud
  filaments score in pickFilamentForSlot.

  CloudStatusBanner silently no-ops on not_authenticated for both clouds;
  expired / unreachable still surface. The not_authenticated i18n keys
  stay in the locale files dormant.

  ConfigureAmsSlotModal: one source badge per row, one colour per source
  (green Local / purple Orca Cloud / bambu-blue Bambu Cloud / amber
  Built-in). The legacy isUser-driven "Custom" badge is gone; every row
  identifies its tier consistently.
maziggy vor 2 Monaten
Ursprung
Commit
d459b6eabb

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
CHANGELOG.md


+ 25 - 43
backend/app/api/routes/slicer_presets.py

@@ -173,11 +173,10 @@ async def _fetch_cloud_presets(
         # one-by-one trips Bambu's limiter and returns 429 on every request
         # for users with large preset libraries (#1150 follow-up).
         #
-        # The dedup pass (see _dedupe_by_name) compensates: when a cloud entry
-        # wins over a same-named local entry, the cloud entry inherits the
-        # local entry's filament_type / filament_colour. So cloud presets that
-        # also exist locally still get metadata-aware pre-pick in the
-        # SliceModal; cloud-only presets fall back to plain priority order.
+        # The metadata-enrich pass (see _enrich_cloud_metadata) compensates:
+        # a Bambu Cloud entry without its own filament_type/colour inherits
+        # those values from a same-named local / orca_cloud / standard entry
+        # so it can still score for type/colour matches in pickFilamentForSlot.
         _cloud_cache[cache_key] = (now, slots)
         return slots, "ok"
     finally:
@@ -420,7 +419,7 @@ async def _resolve_slicer_api_url(db: AsyncSession) -> str | None:
     return url or None
 
 
-def _dedupe_by_name(
+def _enrich_cloud_metadata(
     orca_cloud: dict[str, list[UnifiedPreset]],
     cloud: dict[str, list[UnifiedPreset]],
     local: dict[str, list[UnifiedPreset]],
@@ -431,26 +430,29 @@ def _dedupe_by_name(
     dict[str, list[UnifiedPreset]],
     dict[str, list[UnifiedPreset]],
 ]:
-    """Filter so each preset name appears in exactly one tier.
+    """Backfill Bambu Cloud filament metadata; do NOT dedup tiers.
 
-    Precedence: ``orca_cloud > cloud > local > standard``. Orca Cloud is
-    highest because a user who set up Orca sync is explicitly curating
-    those profiles for use here; Bambu Cloud follows for the same reason
-    one tier down. Order within each tier is preserved.
+    Every tier surfaces its full list — a name that exists in both ``local``
+    and ``orca_cloud`` shows up in BOTH dropdown groups so the user can pick
+    either source. Tier ORDER (``local > orca_cloud > cloud > standard``)
+    is communicated by the SliceModal's group rendering and by the
+    name-collision fallback in ``findPresetByName``; this function does not
+    enforce it.
 
-    Filament metadata merges across tiers: a Bambu Cloud entry without its
-    own ``filament_type`` / ``filament_colour`` (Bambu Cloud doesn't surface
+    Filament metadata merge: a Bambu Cloud entry without its own
+    ``filament_type`` / ``filament_colour`` (Bambu Cloud doesn't surface
     these in the list response for rate-limiting reasons — see
-    :func:`_fetch_cloud_presets`) inherits values from the same-named local
-    or standard entry. Orca Cloud already carries metadata inline, so no
-    backfill is needed for it.
+    :func:`_fetch_cloud_presets`) inherits values from a same-named entry
+    in ``local`` / ``orca_cloud`` / ``standard``. This is the only reason
+    this function exists post-#1712 — without the enrich the Bambu Cloud
+    tier can't score in ``pickFilamentForSlot``.
     """
-    # Build a name → metadata lookup from the tiers that carry it (orca_cloud,
-    # local, standard). Bambu cloud is intentionally skipped — it doesn't
-    # populate filament_type/colour in the list response. Take whichever
-    # non-empty entry shows up first.
+    # Build a name → metadata lookup from the tiers that carry it (local,
+    # orca_cloud, standard). Bambu cloud is intentionally skipped — it
+    # doesn't populate filament_type/colour in the list response. Take
+    # whichever non-empty entry shows up first.
     metadata_by_name: dict[str, tuple[str | None, str | None]] = {}
-    for tier in (orca_cloud, local, standard):
+    for tier in (local, orca_cloud, standard):
         for p in tier["filament"]:
             if p.name in metadata_by_name:
                 continue
@@ -466,27 +468,7 @@ def _dedupe_by_name(
             if p.filament_colour is None and c is not None:
                 p.filament_colour = c
 
-    deduped_cloud = _empty_slots()
-    deduped_local = _empty_slots()
-    deduped_standard = _empty_slots()
-    for slot in ("printer", "process", "filament"):
-        seen = {p.name for p in orca_cloud[slot]}
-        for p in cloud[slot]:
-            if p.name in seen:
-                continue
-            deduped_cloud[slot].append(p)
-            seen.add(p.name)
-        for p in local[slot]:
-            if p.name in seen:
-                continue
-            deduped_local[slot].append(p)
-            seen.add(p.name)
-        for p in standard[slot]:
-            if p.name in seen:
-                continue
-            deduped_standard[slot].append(p)
-            seen.add(p.name)
-    return orca_cloud, deduped_cloud, deduped_local, deduped_standard
+    return orca_cloud, cloud, local, standard
 
 
 @router.get("/printer-models")
@@ -541,7 +523,7 @@ async def list_unified_presets(
     local = await _fetch_local_presets(db)
     standard = await _fetch_bundled_presets(db, refresh=refresh)
 
-    orca_cloud, cloud, local, standard = _dedupe_by_name(orca_cloud, cloud, local, standard)
+    orca_cloud, cloud, local, standard = _enrich_cloud_metadata(orca_cloud, cloud, local, standard)
 
     return UnifiedPresetsResponse(
         orca_cloud=UnifiedPresetsBySlot(**orca_cloud),

+ 8 - 7
backend/app/schemas/slicer_presets.py

@@ -59,13 +59,14 @@ class UnifiedPresetsBySlot(BaseModel):
 
 
 class UnifiedPresetsResponse(BaseModel):
-    """Each tier carries only the names that didn't appear in a higher tier.
-
-    Priority order: ``orca_cloud > cloud > local > standard``. Orca Cloud is
-    highest because it's the most-recently-explicitly-curated source for
-    users who set up Orca sync (they did it on purpose; their Orca picks
-    should outrank everything else). Bambu Cloud follows as the next-most-
-    curated tier. Local imports beat the slicer's stock fallback.
+    """Every tier carries its full preset list — no cross-tier dedup.
+
+    Priority order: ``local > orca_cloud > cloud > standard``. The order
+    drives auto-pick (first non-empty tier wins, name-lookup walks tiers
+    in this order, filament scoring tiebreaks by per-tier bonus) and
+    determines the visual rendering order of the SliceModal's optgroups,
+    but a name that exists in multiple tiers appears in EACH of their
+    groups so the user can pick any source.
 
     ``cloud_status`` / ``orca_cloud_status`` let the frontend show a banner
     explaining why a cloud tier is empty when the user expected to see it

+ 104 - 49
backend/tests/unit/test_slicer_presets.py

@@ -1,9 +1,13 @@
 """Tests for the unified slicer-presets endpoint helpers.
 
-The endpoint stitches together three preset sources (cloud / local /
-standard) with name-based dedup. These tests pin the dedup logic, the
-cloud-status mapping, and the per-user / sidecar caches at the
-helper level — full HTTP integration is covered by the routes test.
+The endpoint stitches together four preset sources (local / orca_cloud /
+cloud / standard). It does NOT dedup across tiers — every tier surfaces
+its full list so the user can pick any source. Bambu Cloud filament
+metadata is enriched from same-named entries in the other tiers so it
+can still score in the SliceModal's auto-pick. These tests pin the
+enrich behaviour, the cloud-status mapping, and the per-user / sidecar
+caches at the helper level — full HTTP integration is covered by the
+routes test.
 """
 
 from __future__ import annotations
@@ -28,50 +32,31 @@ def _slot(items: list[tuple[str, str, str]]) -> dict[str, list[UnifiedPreset]]:
     }
 
 
-class TestDedupeByName:
-    """Cloud > local > standard, by ``name``, order preserved within tier."""
+class TestEnrichCloudMetadata:
+    """No cross-tier dedup — every tier's full list comes back; Bambu Cloud
+    filament metadata is enriched from same-named entries in other tiers."""
 
-    def test_cloud_wins_over_local_and_standard(self):
+    def test_same_name_in_all_tiers_appears_in_every_tier(self):
+        """Critical regression guard for #1712: a user who has imported a
+        local profile AND signed in to Orca AND has Bambu Cloud with the
+        same name should see it under EACH source, not just the highest-
+        priority tier. The order is used for auto-pick + group rendering;
+        it is NOT used to hide profiles."""
+        orca = _slot([("oid1", "Bambu PLA Basic", "orca_cloud")])
         cloud = _slot([("cid1", "Bambu PLA Basic", "cloud")])
         local = _slot([("lid1", "Bambu PLA Basic", "local")])
         standard = _slot([("Bambu PLA Basic", "Bambu PLA Basic", "standard")])
 
-        _oc, c, l_, s = sp._dedupe_by_name(_slot([]), cloud, local, standard)
+        oc, c, l_, s = sp._enrich_cloud_metadata(orca, cloud, local, standard)
 
+        assert [p.source for p in l_["printer"]] == ["local"]
+        assert [p.source for p in oc["printer"]] == ["orca_cloud"]
         assert [p.source for p in c["printer"]] == ["cloud"]
-        assert l_["printer"] == []
-        assert s["printer"] == []
-
-    def test_local_filtered_only_when_present_in_cloud(self):
-        cloud = _slot([("cid1", "Custom PLA", "cloud")])
-        local = _slot(
-            [
-                ("lid1", "Custom PLA", "local"),  # filtered (in cloud)
-                ("lid2", "My Workhorse PLA", "local"),  # kept
-            ]
-        )
-        standard = _slot([])
-
-        _oc, _c, l_, _s = sp._dedupe_by_name(_slot([]), cloud, local, standard)
-        assert [p.name for p in l_["printer"]] == ["My Workhorse PLA"]
-
-    def test_standard_filtered_against_both_higher_tiers(self):
-        cloud = _slot([("c1", "A", "cloud")])
-        local = _slot([("l1", "B", "local")])
-        standard = _slot(
-            [
-                ("A", "A", "standard"),  # filtered (in cloud)
-                ("B", "B", "standard"),  # filtered (in local)
-                ("C", "C", "standard"),  # kept
-            ]
-        )
-
-        _oc, _c, _l, s = sp._dedupe_by_name(_slot([]), cloud, local, standard)
-        assert [p.name for p in s["printer"]] == ["C"]
+        assert [p.source for p in s["printer"]] == ["standard"]
 
     def test_preserves_order_within_tier(self):
-        """A tier's input order must be preserved in its output — nothing in
-        the dedupe pass should sort, reverse, or otherwise reorder entries."""
+        """A tier's input order must be preserved — nothing in the enrich
+        pass should sort, reverse, or otherwise reorder entries."""
         cloud = _slot(
             [
                 ("c1", "Z-First", "cloud"),
@@ -79,25 +64,95 @@ class TestDedupeByName:
                 ("c3", "M-Third", "cloud"),
             ]
         )
-        _oc, c, _l, _s = sp._dedupe_by_name(_slot([]), cloud, _slot([]), _slot([]))
+        _oc, c, _l, _s = sp._enrich_cloud_metadata(_slot([]), cloud, _slot([]), _slot([]))
         assert [p.name for p in c["printer"]] == ["Z-First", "A-Second", "M-Third"]
 
-    def test_dedupe_is_per_slot(self):
-        """A name colliding across DIFFERENT slots must NOT cross-filter —
-        a "Custom" filament shouldn't hide a "Custom" printer."""
+    def test_bambu_cloud_filament_metadata_backfilled_from_local(self):
+        """Bambu Cloud's list response omits filament_type/colour for
+        rate-limit reasons. A same-named local entry's metadata fills in
+        so the cloud entry can still score in pickFilamentForSlot."""
+        local = {
+            "printer": [],
+            "process": [],
+            "filament": [
+                UnifiedPreset(
+                    id="lp1",
+                    name="Bambu PLA Basic",
+                    source="local",
+                    filament_type="PLA",
+                    filament_colour="#FF0000",
+                )
+            ],
+        }
         cloud = {
             "printer": [],
             "process": [],
-            "filament": [UnifiedPreset(id="cf1", name="Custom", source="cloud")],
+            "filament": [UnifiedPreset(id="cp1", name="Bambu PLA Basic", source="cloud")],
+        }
+        _oc, c, _l, _s = sp._enrich_cloud_metadata(_slot([]), cloud, local, _slot([]))
+        # Cloud entry now carries the local entry's metadata.
+        assert c["filament"][0].filament_type == "PLA"
+        assert c["filament"][0].filament_colour == "#FF0000"
+        # Local entry is untouched.
+        assert local["filament"][0].filament_type == "PLA"
+
+    def test_bambu_cloud_metadata_falls_back_through_orca_and_standard(self):
+        """When local doesn't carry the name, orca_cloud / standard fill in."""
+        orca = {
+            "printer": [],
+            "process": [],
+            "filament": [
+                UnifiedPreset(
+                    id="o1",
+                    name="Bambu PLA Basic",
+                    source="orca_cloud",
+                    filament_type="PLA",
+                    filament_colour="#00FF00",
+                )
+            ],
         }
+        cloud = {
+            "printer": [],
+            "process": [],
+            "filament": [UnifiedPreset(id="cp1", name="Bambu PLA Basic", source="cloud")],
+        }
+        _oc, c, _l, _s = sp._enrich_cloud_metadata(orca, cloud, _slot([]), _slot([]))
+        assert c["filament"][0].filament_type == "PLA"
+        assert c["filament"][0].filament_colour == "#00FF00"
+
+    def test_bambu_cloud_keeps_its_own_metadata_when_present(self):
+        """If Bambu Cloud already has filament_type / filament_colour the
+        enrich pass must not overwrite them with a different same-named
+        entry's values."""
         local = {
-            "printer": [UnifiedPreset(id="lp1", name="Custom", source="local")],
+            "printer": [],
+            "process": [],
+            "filament": [
+                UnifiedPreset(
+                    id="lp1",
+                    name="Bambu PLA Basic",
+                    source="local",
+                    filament_type="PETG",
+                    filament_colour="#000000",
+                )
+            ],
+        }
+        cloud = {
+            "printer": [],
             "process": [],
-            "filament": [],
+            "filament": [
+                UnifiedPreset(
+                    id="cp1",
+                    name="Bambu PLA Basic",
+                    source="cloud",
+                    filament_type="PLA",
+                    filament_colour="#FFFFFF",
+                )
+            ],
         }
-        _oc, _c, l_, _s = sp._dedupe_by_name(_slot([]), cloud, local, _slot([]))
-        # The filament-tier collision must NOT remove the printer-tier "Custom".
-        assert [p.name for p in l_["printer"]] == ["Custom"]
+        _oc, c, _l, _s = sp._enrich_cloud_metadata(_slot([]), cloud, local, _slot([]))
+        assert c["filament"][0].filament_type == "PLA"
+        assert c["filament"][0].filament_colour == "#FFFFFF"
 
 
 def _user_with_cloud_auth(user_id: int = 1) -> MagicMock:

+ 8 - 4
frontend/src/__tests__/components/SliceModal.test.tsx

@@ -392,10 +392,15 @@ describe('SliceModal', () => {
     });
   });
 
-  it('renders a "sign in" banner when cloud_status is not_authenticated', async () => {
+  it('omits the cloud banner when status is not_authenticated (#1712)', async () => {
+    // A signed-out user (Bambu or Orca) shouldn't get a permanent "sign in"
+    // nag at the top of every slice. Sign-in lives on the Profiles page; the
+    // modal stays silent unless a previously-signed-in session actually broke
+    // (expired / unreachable).
     mockApi.getSlicerPresets.mockResolvedValue(
       makeUnified({
         cloud_status: 'not_authenticated',
+        orca_cloud_status: 'not_authenticated',
         local: fullThreeTier.local,
         standard: fullThreeTier.standard,
       }),
@@ -405,9 +410,8 @@ describe('SliceModal', () => {
       onClose: vi.fn(),
     });
 
-    await waitFor(() => {
-      expect(screen.getByRole('status')).toHaveTextContent(/Sign in to Bambu Cloud/i);
-    });
+    await waitFor(() => expect(screen.getByText('Imported X1C 0.4')).toBeDefined());
+    expect(screen.queryByRole('status')).toBeNull();
   });
 
   it('renders an "expired" banner when cloud_status is expired', async () => {

+ 3 - 2
frontend/src/api/client.ts

@@ -1464,8 +1464,9 @@ export interface UnifiedPresetsBySlot {
   filament: UnifiedPreset[];
 }
 export interface UnifiedPresetsResponse {
-  // Priority order: orca_cloud > cloud > local > standard. Dedup is applied
-  // backend-side so each name appears in only one tier.
+  // Priority order: local > orca_cloud > cloud > standard. No cross-tier
+  // dedup — every tier surfaces its full list so the user can pick from
+  // any source. The order drives auto-pick + visual group rendering only.
   orca_cloud: UnifiedPresetsBySlot;
   cloud: UnifiedPresetsBySlot;
   local: UnifiedPresetsBySlot;

+ 24 - 13
frontend/src/components/ConfigureAmsSlotModal.tsx

@@ -639,10 +639,11 @@ export function ConfigureAmsSlotModal({
       }
     }
 
-    // Sort: orca_cloud first (user-curated), then cloud user presets, then
-    // cloud built-in, then local, then builtin fallback
+    // Sort: local first (user explicitly imported them), then orca_cloud,
+    // then bambu cloud, then builtin fallback. Matches the SliceModal
+    // tier priority.
     return items.sort((a, b) => {
-      const sourceOrder = { orca_cloud: 0, cloud: 1, local: 2, builtin: 3 };
+      const sourceOrder = { local: 0, orca_cloud: 1, cloud: 2, builtin: 3 };
       if (a.source !== b.source) return sourceOrder[a.source] - sourceOrder[b.source];
       if (a.isUser && !b.isUser) return -1;
       if (!a.isUser && b.isUser) return 1;
@@ -1099,14 +1100,19 @@ export function ConfigureAmsSlotModal({
                                 {t('profiles.localProfiles.badge')}
                               </span>
                             )}
-                            {preset.source === 'builtin' && (
-                              <span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
-                                {t('configureAmsSlot.builtin')}
+                            {preset.source === 'orca_cloud' && (
+                              <span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">
+                                {t('configureAmsSlot.orcaCloud')}
                               </span>
                             )}
-                            {preset.isUser && (
+                            {preset.source === 'cloud' && (
                               <span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
-                                {t('configureAmsSlot.custom')}
+                                {t('configureAmsSlot.bambuCloud')}
+                              </span>
+                            )}
+                            {preset.source === 'builtin' && (
+                              <span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
+                                {t('configureAmsSlot.builtin')}
                               </span>
                             )}
                           </div>
@@ -1334,14 +1340,19 @@ export function ConfigureAmsSlotModal({
                                   {t('profiles.localProfiles.badge')}
                                 </span>
                               )}
-                              {preset.source === 'builtin' && (
-                                <span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
-                                  {t('configureAmsSlot.builtin')}
+                              {preset.source === 'orca_cloud' && (
+                                <span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">
+                                  {t('configureAmsSlot.orcaCloud')}
                                 </span>
                               )}
-                              {preset.isUser && (
+                              {preset.source === 'cloud' && (
                                 <span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
-                                  {t('configureAmsSlot.custom')}
+                                  {t('configureAmsSlot.bambuCloud')}
+                                </span>
+                              )}
+                              {preset.source === 'builtin' && (
+                                <span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
+                                  {t('configureAmsSlot.builtin')}
                                 </span>
                               )}
                             </div>

+ 18 - 26
frontend/src/components/SliceModal.tsx

@@ -38,15 +38,14 @@ interface SliceModalProps {
 
 type Slot = 'printer' | 'process' | 'filament';
 
-// SliceModal-specific tier priority: orca_cloud → local → cloud → standard.
-// Imported (local) profiles are surfaced before Bambu Cloud because they're
-// metadata-tagged (Bambu Cloud isn't, by design — see
-// `_fetch_cloud_presets`'s rate-limit note). Orca Cloud comes first because
-// its sync_pull response inlines metadata too AND represents the user's
-// most-recently-curated source. Standard is the bundled fallback. This is
-// distinct from the listing endpoint's dedup order and only affects what
-// the SliceModal renders / pre-picks.
-const SLICE_MODAL_TIER_ORDER = ['orca_cloud', 'local', 'cloud', 'standard'] as const;
+// Lookup priority: local → orca_cloud → cloud → standard. Local imports
+// outrank everything else because the user explicitly imported them for
+// this install; Orca Cloud comes next; Bambu Cloud after that; standard
+// (bundled) is the final fallback. The backend does NOT dedup tiers —
+// every group renders its full set so the user can pick a same-named
+// preset from a lower-priority source if they want to override the
+// auto-pick.
+const SLICE_MODAL_TIER_ORDER = ['local', 'orca_cloud', 'cloud', 'standard'] as const;
 
 function pickDefault(by: UnifiedPresetsResponse, slot: Slot): PresetRef | null {
   for (const tier of SLICE_MODAL_TIER_ORDER) {
@@ -117,8 +116,8 @@ function pickProcessDefault(
 }
 
 const TIER_BONUS: Record<PresetSource, number> = {
-  orca_cloud: 1.75,
-  local: 1.5,
+  local: 1.75,
+  orca_cloud: 1.5,
   cloud: 1.0,
   standard: 0.5,
 };
@@ -999,7 +998,12 @@ function CloudStatusBanner({
   cloudName?: 'bambu' | 'orca';
 }) {
   const { t } = useTranslation();
-  if (status === 'ok') return null;
+  // `ok` is the happy path. `not_authenticated` is silenced too: a user who
+  // hasn't signed in (or has explicitly logged out — #1712) doesn't need a
+  // permanent nag at the top of the modal; sign-in lives on the Profiles
+  // page if they want it. Only `expired` and `unreachable` surface — those
+  // are real breakage states a previously-signed-in user needs to see.
+  if (status === 'ok' || status === 'not_authenticated') return null;
 
   // Same status vocabulary for both Bambu and Orca Cloud — only the
   // user-facing text varies. The fallbacks below name each cloud explicitly
@@ -1008,10 +1012,6 @@ function CloudStatusBanner({
   const messages =
     cloudName === 'orca'
       ? {
-          not_authenticated: {
-            key: 'slice.orcaCloud.notAuthenticated',
-            fallback: 'Sign in to Orca Cloud (Profiles → Orca Cloud) to see your Orca presets.',
-          },
           expired: {
             key: 'slice.orcaCloud.expired',
             fallback: 'Orca Cloud session expired — sign in again to refresh your Orca presets.',
@@ -1022,10 +1022,6 @@ function CloudStatusBanner({
           },
         }
       : {
-          not_authenticated: {
-            key: 'slice.cloud.notAuthenticated',
-            fallback: 'Sign in to Bambu Cloud (Settings → Profiles → Cloud) to see your cloud presets.',
-          },
           expired: {
             key: 'slice.cloud.expired',
             fallback: 'Bambu Cloud session expired — sign in again to refresh your cloud presets.',
@@ -1036,11 +1032,7 @@ function CloudStatusBanner({
           },
         };
 
-  const tones: Record<Exclude<SlicerCloudStatus, 'ok'>, { tone: string; icon: typeof Cloud }> = {
-    not_authenticated: {
-      tone: 'border-bambu-dark-tertiary/40 bg-bambu-dark text-bambu-gray',
-      icon: Cloud,
-    },
+  const tones: Record<'expired' | 'unreachable', { tone: string; icon: typeof Cloud }> = {
     expired: {
       tone: 'border-amber-700/40 bg-amber-900/20 text-amber-200',
       icon: CloudOff,
@@ -1149,8 +1141,8 @@ function PresetDropdown({
   // empty sections collapse out.
   const { sections, otherEntries } = useMemo(() => {
     const tiers: { key: keyof UnifiedPresetsResponse; label: string; fallback: string }[] = [
-      { key: 'orca_cloud', label: 'slice.tier.orcaCloud', fallback: 'Orca Cloud' },
       { key: 'local', label: 'slice.tier.local', fallback: 'Imported' },
+      { key: 'orca_cloud', label: 'slice.tier.orcaCloud', fallback: 'Orca Cloud' },
       { key: 'cloud', label: 'slice.tier.cloud', fallback: 'Bambu Cloud' },
       { key: 'standard', label: 'slice.tier.standard', fallback: 'Standard' },
     ];

+ 2 - 0
frontend/src/i18n/locales/de.ts

@@ -5246,6 +5246,8 @@ export default {
     noMatchingPresets: 'Keine passenden Voreinstellungen gefunden.',
     custom: 'Benutzerdefiniert',
     builtin: 'Integriert',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Einstellungen an Drucker gesendet',
     filamentProfile: 'Filamentprofil',
     kProfileLabel: 'K-Profil (Pressure Advance)',

+ 2 - 0
frontend/src/i18n/locales/en.ts

@@ -5259,6 +5259,8 @@ export default {
     noMatchingPresets: 'No matching presets found.',
     custom: 'Custom',
     builtin: 'Built-in',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Settings sent to printer',
     filamentProfile: 'Filament Profile',
     kProfileLabel: 'K Profile (Pressure Advance)',

+ 2 - 0
frontend/src/i18n/locales/es.ts

@@ -5255,6 +5255,8 @@ export default {
     noMatchingPresets: 'No se encontraron preajustes coincidentes.',
     custom: 'Personalizado',
     builtin: 'Integrado',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Ajustes enviados a la impresora',
     filamentProfile: 'Perfil de filamento',
     kProfileLabel: 'Perfil K (avance de presión)',

+ 2 - 0
frontend/src/i18n/locales/fr.ts

@@ -5236,6 +5236,8 @@ export default {
     noMatchingPresets: 'Aucun profil trouvé.',
     custom: 'Perso',
     builtin: 'Inclus',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Réglages envoyés',
     filamentProfile: 'Profil Filament',
     kProfileLabel: 'Profil K (Pressure Advance)',

+ 2 - 0
frontend/src/i18n/locales/it.ts

@@ -5235,6 +5235,8 @@ export default {
     noMatchingPresets: 'Nessun preset corrispondente trovato.',
     custom: 'Personalizzato',
     builtin: 'Integrato',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Impostazioni inviate alla stampante',
     filamentProfile: 'Profilo filamento',
     kProfileLabel: 'Profilo K (Pressure Advance)',

+ 2 - 0
frontend/src/i18n/locales/ja.ts

@@ -5247,6 +5247,8 @@ export default {
     noMatchingPresets: '一致するプリセットが見つかりません。',
     custom: 'カスタム',
     builtin: '内蔵',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: '設定をプリンターに送信しました',
     filamentProfile: 'フィラメントプロファイル',
     kProfileLabel: 'Kプロファイル(Pressure Advance)',

+ 2 - 0
frontend/src/i18n/locales/ko.ts

@@ -4939,6 +4939,8 @@ export default {
     noMatchingPresets: '일치하는 프리셋을 찾을 수 없습니다.',
     custom: '사용자 지정',
     builtin: '기본 제공',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: '설정이 프린터로 전송됨',
     filamentProfile: '필라멘트 프로필',
     kProfileLabel: 'K 프로필 (압력 전진)',

+ 2 - 0
frontend/src/i18n/locales/pt-BR.ts

@@ -5235,6 +5235,8 @@ export default {
     noMatchingPresets: 'Nenhuma predefinição correspondente encontrada.',
     custom: 'Personalizado',
     builtin: 'Integrado',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Configurações enviadas para a impressora',
     filamentProfile: 'Perfil de Filamento',
     kProfileLabel: 'Perfil K (Avanço de Pressão)',

+ 2 - 0
frontend/src/i18n/locales/tr.ts

@@ -5190,6 +5190,8 @@ export default {
     noMatchingPresets: 'Eşleşen ön ayar bulunamadı.',
     custom: 'Özel',
     builtin: 'Yerleşik',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: 'Ayarlar yazıcıya gönderildi',
     filamentProfile: 'Filament Profili',
     kProfileLabel: 'K Profili (Basınç İlerlemesi)',

+ 2 - 0
frontend/src/i18n/locales/zh-CN.ts

@@ -5234,6 +5234,8 @@ export default {
     noMatchingPresets: '未找到匹配的预设。',
     custom: '自定义',
     builtin: '内置',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: '设置已发送到打印机',
     filamentProfile: '耗材配置',
     kProfileLabel: 'K 值配置(压力推进)',

+ 2 - 0
frontend/src/i18n/locales/zh-TW.ts

@@ -5234,6 +5234,8 @@ export default {
     noMatchingPresets: '未找到匹配的預設。',
     custom: '自訂',
     builtin: '內建',
+    orcaCloud: 'Orca Cloud',
+    bambuCloud: 'Bambu Cloud',
     settingsSentToPrinter: '設定已傳送到印表機',
     filamentProfile: '耗材設定',
     kProfileLabel: 'K 值設定(壓力推進)',

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
static/assets/index-B3h_dfeT.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-OCdsEuOz.js"></script>
+    <script type="module" crossorigin src="/assets/index-B3h_dfeT.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-7s3X35pi.css">
   </head>
   <body>

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.