Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
6ae635a
feat: server play frontend draft
IMB11 Aug 3, 2026
4697427
fix: build error
IMB11 Sep 4, 2026
67706a6
feat: start on content tab env focused design (#7386)
IMB11 Sep 4, 2026
49303f2
fix: remove feature flag
IMB11 Sep 4, 2026
97e0287
fix: build
IMB11 Sep 4, 2026
48bedf8
dev: notes
IMB11 Sep 4, 2026
78bd203
feat: impl endpoints into panel
IMB11 Sep 10, 2026
e5a2046
qa: 1
IMB11 Sep 10, 2026
86ec91c
fix:
IMB11 Sep 16, 2026
f2b24e1
fix: csp
IMB11 Sep 16, 2026
7942035
fix: archon
IMB11 Sep 16, 2026
f5b84d1
fix: missing features
IMB11 Sep 17, 2026
28ad8fa
fix: qa
IMB11 Sep 20, 2026
cded68e
fix: qa
IMB11 Sep 21, 2026
9c5e407
fix: qa
IMB11 Sep 21, 2026
253dee9
fix: qa
IMB11 Sep 22, 2026
efa77de
chore: qa
IMB11 Sep 22, 2026
978fe9a
fix: qa
IMB11 Sep 22, 2026
b5d6e61
feat: icon editor in server panel
IMB11 Sep 22, 2026
8d07483
fix: qa
IMB11 Sep 22, 2026
85fa5d1
fix: prepr + lint
IMB11 Sep 22, 2026
8876c55
chore: cleanup
IMB11 Sep 22, 2026
4c4a2c2
fix: qa
IMB11 Sep 22, 2026
5d09727
fix: qa
IMB11 Sep 24, 2026
9cf72cf
fix: lint
IMB11 Sep 24, 2026
3ae632b
fix: flags
IMB11 Sep 24, 2026
48a9b50
fix: transitions for onboarding
IMB11 Sep 24, 2026
b86d3f1
feat: fix fetch issues
IMB11 Sep 24, 2026
d1c328f
feat: inviter field support
IMB11 Sep 24, 2026
c221359
fix: qa
IMB11 Sep 24, 2026
dbc80ce
feat: icon api
IMB11 Sep 28, 2026
b637cc0
fix: qa
IMB11 Sep 28, 2026
f636b09
fix: lint
IMB11 Sep 29, 2026
cdd9fb3
feat: qa
IMB11 Sep 30, 2026
71c898c
fix: qa
IMB11 Sep 30, 2026
3cad867
fix: qa
IMB11 Sep 30, 2026
fef2205
fix: qa
IMB11 Sep 30, 2026
8cc4369
fix: qa
IMB11 Sep 30, 2026
0857489
fix: lint
IMB11 Sep 30, 2026
3991822
chore(ci): make macos signing optional
fetchfern Oct 2, 2026
24d8b33
fix apple signing disabled
fetchfern Oct 2, 2026
18e4a4b
fix: fmt after rebase
IMB11 Oct 5, 2026
aeb4755
fix: remove tokens from max use calc
IMB11 Oct 6, 2026
d6dd0f1
fix: panel freeze
IMB11 Oct 6, 2026
22a619f
fix: bug
IMB11 Oct 6, 2026
a98fb70
fix: o11y
IMB11 Oct 6, 2026
971aed1
feat: update toast + modal
IMB11 Oct 7, 2026
4a1faba
fix: qa
IMB11 Oct 7, 2026
620838f
fix: new update qa
IMB11 Oct 8, 2026
d509d07
fix: icon mismatch qa
IMB11 Oct 8, 2026
4790893
fix: qa
IMB11 Oct 8, 2026
82da37e
feat: config file push update modal
IMB11 Oct 8, 2026
0a33ff0
chore: align app-frontend impl with standards
IMB11 Oct 9, 2026
be6c6da
fix: qa
IMB11 Oct 9, 2026
2e4074d
feat: curseforge zip modpack import flow for server onboarding (#7814)
IMB11 Oct 9, 2026
c6fcbba
feat: side type review
fetchfern Oct 12, 2026
068bd0d
fix: qa
IMB11 Oct 13, 2026
b773598
fix: qa
IMB11 Oct 13, 2026
c9e23f4
fix: use original query param
IMB11 Oct 13, 2026
5ed9ca9
feat: release channels for server panel
IMB11 Oct 13, 2026
0c93a91
fix: install flows
IMB11 Oct 13, 2026
ef47739
fix: qa
IMB11 Oct 13, 2026
ce5f413
fix: softOverride: false for onboarding
fetchfern Oct 14, 2026
1ba0782
fix: prepr
IMB11 Oct 14, 2026
1b8dc65
fix: qa
IMB11 Oct 16, 2026
71a1ac5
fix: file drag and droop
IMB11 Oct 19, 2026
c1a8a8e
fix: ci
IMB11 Oct 19, 2026
330f31b
fix: align with saya changes
IMB11 Oct 20, 2026
e703fad
feat: add missing page in cf flow
IMB11 Oct 20, 2026
06f38cf
fix: server icon patch inconsistency
IMB11 Oct 20, 2026
e422c0c
fix: qa
IMB11 Oct 20, 2026
143428b
fix: release channel problems
IMB11 Oct 20, 2026
993c74d
fix: qa
IMB11 Oct 21, 2026
24e451b
fix: qa
IMB11 Oct 21, 2026
b5cc42a
fix: prepr
IMB11 Oct 21, 2026
97dc87f
fix: qa
IMB11 Oct 21, 2026
3d599f1
fix: qa
IMB11 Oct 21, 2026
9911696
fix: reconnect bugs
IMB11 Oct 21, 2026
b6a7717
fix: fmt
IMB11 Oct 21, 2026
87709f8
feat: use sharable files endpoints
IMB11 Oct 21, 2026
f93c345
fix: padding + ui
IMB11 Oct 21, 2026
96bcb03
fix: sqlite lock starvation & excessive row rewrites
fetchfern Oct 22, 2026
a8799ce
fix: use BEGIN IMMEDIATE in writing txn
fetchfern Oct 22, 2026
731816f
fix: don't reject paths starting with `.`
fetchfern Oct 22, 2026
bc007c6
feat: separate job request data from job metadata
fetchfern Oct 22, 2026
2c1c238
fix: qa
IMB11 Oct 23, 2026
11bd287
feat: dropdown feedback temp to test
IMB11 Oct 23, 2026
c01feae
feat: better upload flow
IMB11 Oct 23, 2026
d140e45
feat: push and restart
IMB11 Oct 23, 2026
3455df2
fix: remove divider on files tab of push update modal
IMB11 Oct 23, 2026
e2a8376
feat: change new update flow + introduce migration
IMB11 Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 15 additions & 2 deletions .github/workflows/theseus-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ on:
- 'packages/utils/**'
workflow_dispatch:
inputs:
sign-macos-binaries:
description: Sign macOS binaries
type: boolean
default: true
required: false
sign-windows-binaries:
description: Sign Windows binaries
type: boolean
Expand Down Expand Up @@ -170,9 +175,9 @@ jobs:
dasel delete -f apps/app/tauri-release.conf.json 'bundle.windows.signCommand'
fi

- name: Build macOS app
- name: Build signed macOS app
run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-dev.conf.json' }}
if: contains(matrix.artifact-target-name, 'darwin')
if: contains(matrix.artifact-target-name, 'darwin') && (github.event_name != 'workflow_dispatch' || inputs.sign-macos-binaries)
env:
TAURI_BUNDLER_DMG_IGNORE_CI: 'true'
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
Expand All @@ -185,6 +190,14 @@ jobs:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}

- name: Build unsigned macOS app
run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config tauri-dev.conf.json' }}
if: contains(matrix.artifact-target-name, 'darwin') && github.event_name == 'workflow_dispatch' && !inputs.sign-macos-binaries
env:
TAURI_BUNDLER_DMG_IGNORE_CI: 'true'
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}

- name: Build Linux app
run: ${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || inputs.app-version-override != '') && 'pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json' || 'pnpm --filter=@modrinth/app run tauri build --config tauri-dev.conf.json' }}
if: contains(matrix.artifact-target-name, 'linux')
Expand Down
99 changes: 61 additions & 38 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,27 @@ import {
commonSettingsMessages,
ContentInstallModal,
ContentUpdaterModal,
createServerInviteHandoff,
createServerOnboardingFlow,
CreationFlowModal,
defineMessages,
I18nDebugPanel,
IconButton,
LoadingBar,
NotificationPanel,
PopupNotificationPanel,
provideFileDownload,
provideIconCache,
provideLoadingState,
provideModalBehavior,
provideModrinthClient,
provideNotificationManager,
providePageContext,
providePopupNotificationManager,
provideServerInviteHandoff,
provideServerOnboardingFlow,
provideServerPlay,
ServerOnboardingModal,
TeleportOverflowMenu,
TooltipDirective,
useDebugLogger,
Expand All @@ -64,6 +72,7 @@ import { RouterView, useRoute, useRouter } from 'vue-router'
import { useRouteLoading } from '@/app/runtime/use-route-loading'
import AppSidebar from '@/app/shell/sidebar/index.vue'
import AppTitleBar from '@/app/shell/title-bar/index.vue'
import { trackExternalFileDownload } from '@/components/ui/download-manager/external-file-downloads'
import ErrorModal from '@/components/ui/ErrorModal.vue'
import HostingUpdateRequired from '@/components/ui/HostingUpdateRequired.vue'
import AddServerToInstanceModal from '@/components/ui/install_flow/AddServerToInstanceModal.vue'
Expand All @@ -88,6 +97,12 @@ import { useInstanceMetadataRefresh } from '@/composables/use-instance-metadata-
import { useQuickInstanceLimit } from '@/composables/use-quick-instance-limit.ts'
import { isDarkTheme, useTheme } from '@/composables/use-theme.ts'
import { config } from '@/config'
import ServerPlayHandler from '@/features/hosting/play-handler.vue'
import { prefetchHostingData } from '@/features/hosting/prefetch'
import {
createInstanceLaunchState,
provideInstanceLaunchState,
} from '@/features/instances/launch-state'
import { provideMinecraftAccounts } from '@/features/minecraft-accounts/context'
import AppSettingsModal from '@/features/settings/app-settings-modal/index.vue'
import { provideSettingsActions } from '@/features/settings/settings-actions'
Expand Down Expand Up @@ -157,6 +172,8 @@ import {
instanceListQueryOptions,
screenshotKeys,
} from '@/pages/instance/query-options'
import { createFileDownload } from '@/platform/adapters/file-download'
import { createIconCache } from '@/platform/adapters/icon-cache'
import { createAppClient } from '@/platform/modrinth-client'
import {
appUpdateState,
Expand Down Expand Up @@ -283,6 +300,12 @@ useAppEvent('ads_consent_required', handleAdsConsentRequired, appEvents)

const tauriApiClient = createAppClient()
provideModrinthClient(tauriApiClient)
provideServerOnboardingFlow(createServerOnboardingFlow())
provideServerInviteHandoff(createServerInviteHandoff())
provideFileDownload(createFileDownload(tauriApiClient, trackExternalFileDownload))
const iconCache = createIconCache()
provideIconCache(iconCache)
provideInstanceLaunchState(createInstanceLaunchState())
const { data: authenticatedModrinthUser } = useQuery({
queryKey: computed(() => ['authenticated-user', 'campaigns', credentials.value?.user?.id]),
queryFn: () => tauriApiClient.labrinth.users_v3.getAuthenticated(),
Expand Down Expand Up @@ -804,8 +827,14 @@ async function setupApp() {
}

const stateFailed = ref(false)
const { loading, canNavigateBack, canNavigateForward, onSuspensePending, onSuspenseResolve } =
useRouteLoading({ stateInitialized, stateFailed })
const {
loading,
canNavigateBack,
canNavigateForward,
onboardingPageReady,
onSuspensePending,
onSuspenseResolve,
} = useRouteLoading({ stateInitialized, stateFailed })
provideLoadingState(loading)

traceStartupStep('Initialize backend state', () => initialize_state(appEventChannel))
Expand All @@ -829,42 +858,19 @@ const handleClose = async () => {

const queryClient = useQueryClient()

watch(stateInitialized, (ready) => {
if (ready) {
queryClient.prefetchQuery({
queryKey: ['servers'],
queryFn: async () => {
const response = await tauriApiClient.archon.servers_v0.list({ limit: 100 })
const hasMedalServers = response.servers.some((s) => s.is_medal)
if (hasMedalServers) {
const subscriptions = await tauriApiClient.labrinth.billing_internal.getSubscriptions()
for (const server of response.servers) {
if (server.is_medal) {
const sub = subscriptions.find((s) => s.metadata?.id === server.server_id)
if (sub) {
server.medal_expires = new Date(
new Date(sub.created).getTime() + 5 * 86400000,
).toISOString()
}
}
}
}
return response
},
staleTime: 30_000,
})
queryClient.prefetchQuery({
queryKey: ['billing', 'subscriptions'],
queryFn: () => tauriApiClient.labrinth.billing_internal.getSubscriptions(),
staleTime: 30_000,
})
queryClient.prefetchQuery({
queryKey: ['billing', 'payments'],
queryFn: () => tauriApiClient.labrinth.billing_internal.getPayments(),
staleTime: 30_000,
})
}
})
watch(
() => stateInitialized.value && credentials.value?.session,
(session) => {
if (!session) return

void prefetchHostingData(
queryClient,
tauriApiClient,
iconCache,
() => credentials.value?.session === session,
)
},
)

const error = useError()
const errorModal = ref()
Expand Down Expand Up @@ -954,6 +960,14 @@ const contentInstallModpackAlreadyInstalledModal = ref()
const addServerToInstanceModal = ref()
const incompatibilityWarningModal = ref()
const installToPlayModal = ref()
const serverPlayHandler = ref()
const serverPlay = {
async play(target) {
if (!serverPlayHandler.value) throw new Error('Server play handler is not ready.')
await serverPlayHandler.value.play(target)
},
}
provideServerPlay(serverPlay)
const sharedInstanceInviteHandler = ref()
const updateToPlayModal = ref()

Expand Down Expand Up @@ -1535,6 +1549,8 @@ async function handleCommand(e) {
} else {
await run(e.id).catch(handleError)
}
} else if (e.event === 'PlayHostingServer') {
await serverPlay.play({ serverId: e.server_id, worldId: e.world_id }).catch(handleError)
} else if (e.event === 'InstallSharedInstanceInvite') {
await sharedInstanceInviteHandler.value?.installFromInviteId(e.invite_id)
} else if (e.event === 'InstallServer') {
Expand Down Expand Up @@ -2001,6 +2017,12 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
@create="handleCreate"
@browse-modpacks="handleBrowseModpacks"
/>
<ServerOnboardingModal
browse-path="/browse/modpack"
:get-loader-manifest="getLoaderManifest"
:navigate="(to) => router.push(to)"
:page-ready="onboardingPageReady"
/>
<IconEditorModal
ref="creationIconEditorModal"
:config="creationGeneratedIcon?.config"
Expand Down Expand Up @@ -2270,6 +2292,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
@create-anyway="handleContentInstallModpackDuplicateCreateAnyway"
@go-to-instance="handleContentInstallModpackDuplicateGoToInstance"
/>
<ServerPlayHandler ref="serverPlayHandler" />
<SharedInstanceInviteHandler ref="sharedInstanceInviteHandler" />
<InstallToPlayModal ref="installToPlayModal" :show-external-warnings="false" />
<UpdateToPlayModal ref="updateToPlayModal" :show-external-warnings="false" />
Expand Down
12 changes: 11 additions & 1 deletion apps/app-frontend/src/app/runtime/use-route-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function useRouteLoading({ stateInitialized, stateFailed }: RouteLoadingO
const route = useRoute()
const canNavigateBack = ref(false)
const canNavigateForward = ref(false)
const onboardingPageReady = ref(true)
let initialLoadToken: symbol | null = null
let routerToken: symbol | null = null
let suspenseToken: symbol | null = null
Expand Down Expand Up @@ -83,6 +84,7 @@ export function useRouteLoading({ stateInitialized, stateFailed }: RouteLoadingO
})

function onSuspensePending() {
onboardingPageReady.value = false
debugStartup('Route Suspense pending', { route: route.path })
suspensePending = true
viewportScroll.onRoutePending()
Expand All @@ -91,6 +93,7 @@ export function useRouteLoading({ stateInitialized, stateFailed }: RouteLoadingO
}

function onSuspenseResolve() {
onboardingPageReady.value = true
debugStartup('Route Suspense resolved', { route: route.path })
viewportScroll.onRouteResolved()
if (suspenseToken) {
Expand Down Expand Up @@ -120,5 +123,12 @@ export function useRouteLoading({ stateInitialized, stateFailed }: RouteLoadingO
}
})

return { loading, canNavigateBack, canNavigateForward, onSuspensePending, onSuspenseResolve }
return {
loading,
canNavigateBack,
canNavigateForward,
onboardingPageReady,
onSuspensePending,
onSuspenseResolve,
}
}
4 changes: 2 additions & 2 deletions apps/app-frontend/src/components/ui/HostingUpdateRequired.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { DownloadIcon, ExcitedRinthbot, RefreshCwIcon, ServerStackIcon } from '@modrinth/assets'
import { Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
import { Button, defineMessages, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'

import {
Expand Down Expand Up @@ -42,7 +42,7 @@ const messages = defineMessages({
useRootBreadcrumb({
slot: 'root',
id: 'servers',
label: () => formatMessage(commonMessages.serversLabel),
label: 'Hosting',
to: '/hosting/manage/',
visual: { type: 'icon', component: ServerStackIcon },
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ defineEmits<{ close: [] }>()

const messages = defineMessages({
title: { id: 'app.download-manager.title', defaultMessage: 'Download manager' },
show: { id: 'app.download-manager.show', defaultMessage: 'Show installation tasks' },
hide: { id: 'app.download-manager.hide', defaultMessage: 'Hide installation tasks' },
show: { id: 'app.download-manager.show-tasks', defaultMessage: 'Show tasks' },
hide: { id: 'app.download-manager.hide-tasks', defaultMessage: 'Hide tasks' },
attention: { id: 'app.download-manager.attention', defaultMessage: 'Needs attention' },
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CheckIcon,
CopyIcon,
DownloadIcon,
MinecraftServerIcon,
PauseIcon,
PlayIcon,
TrashIcon,
Expand All @@ -21,6 +22,7 @@ import {
ProgressBar,
truncatedTooltip,
useRelativeTime,
useServerIcon,
useVIntl,
} from '@modrinth/ui'
import { computed, useTemplateRef } from 'vue'
Expand All @@ -29,6 +31,10 @@ import { RouterLink } from 'vue-router'
import type { DownloadManagerJob } from './use-download-manager'

const props = defineProps<{ job: DownloadManagerJob }>()
const { icon: serverIcon } = useServerIcon(() => props.job.serverId ?? '')
const displayIcon = computed(() =>
props.job.serverId ? (serverIcon.value ?? MinecraftServerIcon) : props.job.iconUrl,
)
defineEmits<{
retry: [id: string]
cancel: [id: string]
Expand Down Expand Up @@ -95,8 +101,8 @@ const instanceLink = computed(() =>
@click="instanceLink && $emit('open')"
>
<Avatar
v-if="job.iconUrl"
:src="job.iconUrl"
v-if="displayIcon"
:src="displayIcon"
size="36px"
no-shadow
class="!rounded-xl border border-solid border-surface-5"
Expand Down Expand Up @@ -126,7 +132,7 @@ const instanceLink = computed(() =>
{{ job.title }}
</span>
<span
v-if="job.taskType && job.taskType !== job.text"
v-if="job.kind !== 'external-file' && job.taskType && job.taskType !== job.text"
class="truncate text-xs font-medium leading-4 text-primary"
>
{{ job.taskType }}
Expand All @@ -141,6 +147,12 @@ const instanceLink = computed(() =>
: 'text-primary'
"
>
<template
v-if="job.kind === 'external-file' && job.taskType && job.taskType !== job.text"
>
<span class="min-w-0 truncate text-primary">{{ job.taskType }}</span>
<BulletDivider class="shrink-0 text-primary" />
</template>
<template v-if="complete && job.finishedAt">
<time :datetime="job.finishedAt" class="shrink-0">
{{ formatRelativeTime(job.finishedAt) }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const messages = defineMessages({
defaultMessage: 'Clear all completed tasks',
},
complete: { id: 'app.download-manager.complete', defaultMessage: 'Complete' },
empty: { id: 'app.download-manager.empty', defaultMessage: 'No installation tasks' },
empty: { id: 'app.download-manager.no-tasks', defaultMessage: 'No tasks' },
rate: { id: 'app.download-manager.estimated-rate', defaultMessage: 'Estimated download speed' },
})
const panel = useTemplateRef('panel')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { reactive, ref } from 'vue'

export interface ExternalFileDownloadProgress {
stage: 'waiting' | 'downloading' | 'saving'
downloadedBytes: number
totalBytes: number | null
}
import type { ExternalFileProgress } from '@/platform/app-lib/files/commands'

export interface ExternalFileDownloadTask {
id: string
filename: string
serverId?: string
serverName?: string
status: 'running' | 'succeeded' | 'failed' | 'canceled'
stage: 'preparing' | ExternalFileDownloadProgress['stage']
stage: 'preparing' | ExternalFileProgress['stage']
downloadedBytes: number
totalBytes: number | null
rate: number
Expand Down Expand Up @@ -66,7 +62,7 @@ export function trackExternalFileDownload(
}

return {
update(progress: ExternalFileDownloadProgress) {
update(progress: ExternalFileProgress) {
if (task.status !== 'running' || task.canceling) return
const now = performance.now()
if (progress.stage !== 'downloading' || progress.downloadedBytes < previousBytes) {
Expand Down
Loading
Loading