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.
This commit is contained in:
maziggy
2026-06-11 12:17:45 +02:00
parent 4ab7339fe4
commit d459b6eabb
21 changed files with 272 additions and 202 deletions
@@ -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
View File
@@ -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;
@@ -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,16 +1100,21 @@ export function ConfigureAmsSlotModal({
{t('profiles.localProfiles.badge')}
</span>
)}
{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.source === 'cloud' && (
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
{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>
)}
{preset.isUser && (
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
{t('configureAmsSlot.custom')}
</span>
)}
</div>
</div>
</button>
@@ -1334,16 +1340,21 @@ export function ConfigureAmsSlotModal({
{t('profiles.localProfiles.badge')}
</span>
)}
{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.source === 'cloud' && (
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
{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>
)}
{preset.isUser && (
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
{t('configureAmsSlot.custom')}
</span>
)}
</div>
</div>
</button>
+18 -26
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -5247,6 +5247,8 @@ export default {
noMatchingPresets: '一致するプリセットが見つかりません。',
custom: 'カスタム',
builtin: '内蔵',
orcaCloud: 'Orca Cloud',
bambuCloud: 'Bambu Cloud',
settingsSentToPrinter: '設定をプリンターに送信しました',
filamentProfile: 'フィラメントプロファイル',
kProfileLabel: 'Kプロファイル(Pressure Advance)',
+2
View File
@@ -4939,6 +4939,8 @@ export default {
noMatchingPresets: '일치하는 프리셋을 찾을 수 없습니다.',
custom: '사용자 지정',
builtin: '기본 제공',
orcaCloud: 'Orca Cloud',
bambuCloud: 'Bambu Cloud',
settingsSentToPrinter: '설정이 프린터로 전송됨',
filamentProfile: '필라멘트 프로필',
kProfileLabel: 'K 프로필 (압력 전진)',
+2
View File
@@ -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
View File
@@ -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
View File
@@ -5234,6 +5234,8 @@ export default {
noMatchingPresets: '未找到匹配的预设。',
custom: '自定义',
builtin: '内置',
orcaCloud: 'Orca Cloud',
bambuCloud: 'Bambu Cloud',
settingsSentToPrinter: '设置已发送到打印机',
filamentProfile: '耗材配置',
kProfileLabel: 'K 值配置(压力推进)',
+2
View File
@@ -5234,6 +5234,8 @@ export default {
noMatchingPresets: '未找到匹配的預設。',
custom: '自訂',
builtin: '內建',
orcaCloud: 'Orca Cloud',
bambuCloud: 'Bambu Cloud',
settingsSentToPrinter: '設定已傳送到印表機',
filamentProfile: '耗材設定',
kProfileLabel: 'K 值設定(壓力推進)',