Skip to content

feat(splash): refresh startup experience - #2048

Merged
zhangmo8 merged 5 commits into
devfrom
feat/splash-experience
Jul 28, 2026
Merged

feat(splash): refresh startup experience#2048
zhangmo8 merged 5 commits into
devfrom
feat/splash-experience

Conversation

@zhangmo8

@zhangmo8 zhangmo8 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the textual startup splash with an animated DeepChat logo composition
  • distinguish circular loading/system-unlock states from the functional manual-unlock state
  • add focused splash renderer coverage and the feature SDD record

UI layout

BEFORE

┌──────────────────────────────────┐
│       DeepChat loading text       │
│       activity progress feed      │
└──────────────────────────────────┘

AFTER

      ◯ animated DeepChat logo
   circular loading/system-unlock

┌──────────────────────────────────┐
│       manual password unlock      │
└──────────────────────────────────┘

Validation

  • pnpm run format
  • pnpm run i18n
  • pnpm run lint
  • pnpm exec vitest run test/renderer/splash/loading.test.ts
  • pnpm run typecheck:web

Summary by CodeRabbit

  • New Features
    • Introduced a redesigned orb-style splash experience with animated aurora visuals and branded logos.
    • Added distinct visual states for loading, automatic system unlock, and manual database unlock.
    • Added reduced-motion support for accessibility.
  • Bug Fixes
    • Preserved unlock progress, request, submit, and cancel interactions.
  • Tests
    • Added coverage for splash state transitions and unlock panel visibility.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d163ac77-b54f-402e-ad1f-02e605a1b2a0

📥 Commits

Reviewing files that changed from the base of the PR and between 3f42217 and 84960f8.

📒 Files selected for processing (1)
  • src/renderer/splash/loading.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/splash/loading.vue

📝 Walkthrough

Walkthrough

The splash renderer replaces the previous activity-based loading view with animated logo and aurora compositions, distinguishes system and manual unlock states, preserves unlock callbacks, adds reduced-motion styling, and introduces focused renderer tests and feature documentation.

Changes

Splash experience redesign

Layer / File(s) Summary
Splash contract and state rendering
docs/features/splash-experience/*, src/renderer/splash/loading.vue
The feature documents specify orb-based loading and unlock states, while the component renders distinct loading, system-unlock, and manual-unlock compositions using local SVG logos and unlock event callbacks.
Orb layout and animation system
src/renderer/splash/loading.vue
Styles add full-viewport orb layouts, aurora pools and ribbons, logo animation effects, updated unlock controls, and reduced-motion overrides.
Renderer state validation
test/renderer/splash/loading.test.ts
Tests mock unlock callbacks and verify the initial loading state plus system-credential and manual-unlock transitions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DeepChatSplash
  participant Loading
  participant RendererDOM
  DeepChatSplash->>Loading: onUnlockProgress(payload)
  Loading->>RendererDOM: render system unlock panel
  DeepChatSplash->>Loading: onUnlockRequest(payload)
  Loading->>RendererDOM: render manual unlock panel
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the splash/startup experience redesign and is concise and specific enough.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/splash-experience

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (9)
src/renderer/splash/loading.vue (7)

107-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

animationStarted is a constant. It's initialized to true and never mutated, so the binding always resolves to the same class. Either drop the ref and hardcode loader-stage--orb loader-stage--animating, or flip it on mount (likely the intent, to avoid animations starting before first paint).

♻️ Option: start on mount
-const animationStarted = ref(true)
+const animationStarted = ref(false)
 onMounted(() => {
+  requestAnimationFrame(() => {
+    animationStarted.value = true
+  })
   cleanupListeners.push(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` at line 107, Update animationStarted in the
loading component: either remove the unused ref and always apply both
loader-stage--orb and loader-stage--animating classes, or initialize it as
inactive and set it to true during the component’s mount lifecycle so animation
starts after first paint.

900-1033: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Nine unused keyframes. card-settle, rim-breathe, wake-sweep, wake-idle, fish-swim, fish-idle, eye-arrive, speed-line, and wake-pulse are not referenced by any selector in this file — roughly 130 lines of dead CSS in the startup bundle. The speed-line keyframe is especially confusing since the .speed-line class actually uses speed-scan. Please drop them (or wire them up if they were intended for the loader).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 900 - 1033, Remove the unused
keyframes card-settle, rim-breathe, wake-sweep, wake-idle, fish-swim, fish-idle,
eye-arrive, speed-line, and wake-pulse from the stylesheet; preserve the
existing animation referenced by the .speed-line selector, which uses
speed-scan.

4-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Aurora background markup duplicated three times. Consider extracting a small AuroraBackground component (with an optional cyan prop) to keep the three stages in sync.

Also applies to: 44-49, 69-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 4 - 9, Extract the repeated
aurora markup into a reusable AuroraBackground component, supporting an optional
cyan prop for the variant used by specific stages. Replace all three duplicated
blocks in the loading view with this component so the background structure
remains synchronized.

15-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Splash strings are hardcoded English. DeepChat is starting, Local database is encrypted, SQLite password, Unlock/Quit, and the hint/subtitle text are user-facing. Guidelines require vue-i18n keys under src/renderer/src/i18n; if the splash entry intentionally runs without the i18n runtime (to keep startup cheap), please note that exception in the PR.

As per coding guidelines: "All user-facing strings must use vue-i18n keys defined under src/renderer/src/i18n".

Also applies to: 35-37, 55-59, 67-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 15 - 17, Replace all hardcoded
user-facing splash text in the loading component—including the title, encryption
message, password label, hint/subtitle, and Unlock/Quit controls—with vue-i18n
keys defined under src/renderer/src/i18n, using the component’s existing i18n
access pattern or adding the required setup. If this splash intentionally runs
without the i18n runtime, document that exception in the PR instead.

Source: Coding guidelines


63-68: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

aria-label on a plain div isn't reliably exposed. Without a role, most AT ignores the label. Add role="status" (or role="img") so the starting state is announced.

♿ Proposed fix
     <div
       v-else
       class="loader-stage loader-stage--orb"
       :class="{ 'loader-stage--animating': animationStarted }"
+      role="status"
       aria-label="DeepChat is starting"
     >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 63 - 68, Add an explicit
accessibility role to the loading-state div in the `loader-stage--orb` branch,
using `role="status"` so assistive technology reliably announces its existing
`aria-label` of “DeepChat is starting.”

506-529: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Base .unlock-panel styling is dead. Both consumers (--system, --manual) reset border, background, box-shadow, and backdrop-filter, and the light-mode override at Lines 569–575 restyles properties that are then cleared. Collapse to just the layout rules.

♻️ Proposed cleanup
 .unlock-panel {
   position: relative;
   z-index: 1;
   display: flex;
   width: min(340px, 100%);
   flex-direction: column;
   gap: 11px;
-  border: 1px solid rgb(148 163 184 / 28%);
-  border-radius: 20px;
-  background: linear-gradient(145deg, rgb(10 30 71 / 84%), rgb(3 12 33 / 82%));
-  box-shadow:
-    0 18px 48px rgb(2 8 23 / 34%),
-    inset 0 1px rgb(255 255 255 / 8%);
   padding: 22px 24px 24px;
-  backdrop-filter: blur(22px);
 }
 
-.unlock-panel--system,
-.unlock-panel--manual {
-  border: 0;
-  background: transparent;
-  box-shadow: none;
-  backdrop-filter: none;
-}
-
 .unlock-panel--system {
 `@media` (prefers-color-scheme: light) {
-  .unlock-panel {
-    border-color: rgb(45 116 181 / 20%);
-    background: rgb(255 255 255 / 72%);
-    box-shadow:
-      0 18px 48px rgb(67 125 178 / 18%),
-      inset 0 1px rgb(255 255 255 / 74%);
-  }
-
   .unlock-title {

Also applies to: 569-575

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 506 - 529, Remove the unused
visual properties from the base .unlock-panel rule, retaining only
layout-related styling used by both .unlock-panel--system and
.unlock-panel--manual. Remove the corresponding light-mode override declarations
for the cleared border, background, box-shadow, and backdrop-filter properties,
while preserving the shared layout and spacing behavior.

258-263: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Viewport-relative sizes inside a 340px orb. 135vmax ribbons and min(70vmax, 820px) pools are clipped by the 340px circle, so the visual result depends heavily on window size rather than on the orb. Consider sizing these relative to the stage (e.g. % of the aurora container) so loading and system-unlock look identical across displays.

Also applies to: 289-297

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/splash/loading.vue` around lines 258 - 263, Replace the
viewport-relative dimensions used by .aurora-ribbon and the related pool sizing
around the aurora container with stage-relative percentages or equivalent
container-based sizing. Keep the elements contained within the 340px orb while
ensuring loading and system-unlock visuals remain consistent across viewport
sizes.
test/renderer/splash/loading.test.ts (2)

44-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assertions are weak for the states this PR introduces. Line 47 is tautological (.get('.splash-shell') then asserting it has splash-shell), and the "circular shell" test never checks the orb class. Assert unlock-stage--orb / loader-stage--orb so a regression to a rectangular stage actually fails.

💚 Proposed strengthening
   it('starts with the circular splash shell loading state', () => {
     const wrapper = mountLoading()
 
-    expect(wrapper.get('.splash-shell').classes()).toContain('splash-shell')
+    expect(wrapper.get('.loader-stage').classes()).toContain('loader-stage--orb')
     expect(wrapper.get('.loader-stage').attributes('aria-label')).toBe('DeepChat is starting')
     expect(wrapper.classes()).not.toContain('splash-shell--manual-unlock')
   })
-    expect(wrapper.get('.splash-shell').classes()).toContain('splash-shell')
+    expect(wrapper.get('.unlock-stage').classes()).toContain('unlock-stage--orb')
     expect(wrapper.classes()).not.toContain('splash-shell--manual-unlock')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/renderer/splash/loading.test.ts` around lines 44 - 62, Strengthen the
two loading-state tests in the relevant describe block: replace the tautological
splash-shell class assertion with an assertion that the wrapper contains
unlock-stage--orb, and add an assertion that the loader stage contains
loader-stage--orb. Preserve the existing accessibility, manual-unlock, and
system-unlock assertions.

45-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Local wrapper shadows the module-level one. Cleanup still works because mountLoading assigns the outer binding, but the shadowing is fragile and OxLint's no-shadow will likely flag it. Drop the const.

♻️ Proposed fix
-    const wrapper = mountLoading()
+    const view = mountLoading()

Also applies to: 53-53, 65-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/renderer/splash/loading.test.ts` at line 45, Remove the local const
declaration before each affected mountLoading call so the module-level wrapper
binding is reused. Update all occurrences noted in the comment, including the
calls around the existing tests, while preserving the current mounting and
cleanup behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/features/splash-experience/tasks.md`:
- Around line 6-7: Update the checklist in the splash experience tasks document
to mark focused renderer tests and required validation as completed, reflecting
the existing loading renderer test and reported format, i18n, lint, Vitest, and
typecheck validation.

In `@src/renderer/splash/loading.vue`:
- Around line 289-309: Wire the existing aurora animation keyframes to the
relevant `.aurora-pool` and sweep selectors, ensuring each pool receives the
appropriate animation with its centering `translate3d` transform and timing.
Remove or avoid the unused `will-change: transform` only if the animations are
not retained; keep the existing gradient styling unchanged.
- Around line 1035-1050: Update the prefers-reduced-motion block to disable
animations for .unlock-logo and .unlock-logo :deep(path:nth-of-type(3)),
covering the unlock-logo-float and eye-blink animations. Remove the nonexistent
.logo-loader__wake selector from the same block.
- Around line 628-640: Update the .unlock-input:focus rule to add a clearly
visible focus ring, while preserving the existing border-color change. Use an
accessible box-shadow or equivalent focus indicator that provides sufficient
contrast around the password input.

---

Nitpick comments:
In `@src/renderer/splash/loading.vue`:
- Line 107: Update animationStarted in the loading component: either remove the
unused ref and always apply both loader-stage--orb and loader-stage--animating
classes, or initialize it as inactive and set it to true during the component’s
mount lifecycle so animation starts after first paint.
- Around line 900-1033: Remove the unused keyframes card-settle, rim-breathe,
wake-sweep, wake-idle, fish-swim, fish-idle, eye-arrive, speed-line, and
wake-pulse from the stylesheet; preserve the existing animation referenced by
the .speed-line selector, which uses speed-scan.
- Around line 4-9: Extract the repeated aurora markup into a reusable
AuroraBackground component, supporting an optional cyan prop for the variant
used by specific stages. Replace all three duplicated blocks in the loading view
with this component so the background structure remains synchronized.
- Around line 15-17: Replace all hardcoded user-facing splash text in the
loading component—including the title, encryption message, password label,
hint/subtitle, and Unlock/Quit controls—with vue-i18n keys defined under
src/renderer/src/i18n, using the component’s existing i18n access pattern or
adding the required setup. If this splash intentionally runs without the i18n
runtime, document that exception in the PR instead.
- Around line 63-68: Add an explicit accessibility role to the loading-state div
in the `loader-stage--orb` branch, using `role="status"` so assistive technology
reliably announces its existing `aria-label` of “DeepChat is starting.”
- Around line 506-529: Remove the unused visual properties from the base
.unlock-panel rule, retaining only layout-related styling used by both
.unlock-panel--system and .unlock-panel--manual. Remove the corresponding
light-mode override declarations for the cleared border, background, box-shadow,
and backdrop-filter properties, while preserving the shared layout and spacing
behavior.
- Around line 258-263: Replace the viewport-relative dimensions used by
.aurora-ribbon and the related pool sizing around the aurora container with
stage-relative percentages or equivalent container-based sizing. Keep the
elements contained within the 340px orb while ensuring loading and system-unlock
visuals remain consistent across viewport sizes.

In `@test/renderer/splash/loading.test.ts`:
- Around line 44-62: Strengthen the two loading-state tests in the relevant
describe block: replace the tautological splash-shell class assertion with an
assertion that the wrapper contains unlock-stage--orb, and add an assertion that
the loader stage contains loader-stage--orb. Preserve the existing
accessibility, manual-unlock, and system-unlock assertions.
- Line 45: Remove the local const declaration before each affected mountLoading
call so the module-level wrapper binding is reused. Update all occurrences noted
in the comment, including the calls around the existing tests, while preserving
the current mounting and cleanup behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d183e141-305d-4fdf-8756-b5d2d86e189d

📥 Commits

Reviewing files that changed from the base of the PR and between cd2152c and 3d627e8.

⛔ Files ignored due to path filters (2)
  • src/renderer/src/assets/splash/logo-v3-dark.svg is excluded by !**/*.svg
  • src/renderer/src/assets/splash/logo-v3-light.svg is excluded by !**/*.svg
📒 Files selected for processing (5)
  • docs/features/splash-experience/plan.md
  • docs/features/splash-experience/spec.md
  • docs/features/splash-experience/tasks.md
  • src/renderer/splash/loading.vue
  • test/renderer/splash/loading.test.ts

Comment thread docs/features/splash-experience/tasks.md Outdated
Comment thread src/renderer/splash/loading.vue
Comment thread src/renderer/splash/loading.vue
Comment thread src/renderer/splash/loading.vue
@zhangmo8
zhangmo8 merged commit 082edea into dev Jul 28, 2026
12 checks passed
@zhangmo8
zhangmo8 deleted the feat/splash-experience branch July 28, 2026 05:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant