From debf62f76e471efb3904aea8675e5de87e694cf3 Mon Sep 17 00:00:00 2001 From: ian Date: Thu, 16 Apr 2026 09:24:26 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=B5=EA=B0=9C:=20KoTalk=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=20=EA=B8=B0=EC=A4=80=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 11 + .github/ISSUE_TEMPLATE/bug_report.md | 39 + .github/ISSUE_TEMPLATE/config.yml | 14 + .github/ISSUE_TEMPLATE/feature_request.md | 35 + .github/ISSUE_TEMPLATE/ux_review.md | 34 + .github/PULL_REQUEST_TEMPLATE.md | 17 + .github/workflows/ci.yml | 52 + .github/workflows/release-portable.yml | 231 + .gitignore | 13 + ARCHITECTURE.md | 77 + BACKGROUND.md | 76 + BRANCHING_STRATEGY.md | 123 + BUSINESS_MODEL.md | 37 + CHANGELOG.md | 93 + CODE_OF_CONDUCT.md | 52 + COMMUNITY.md | 30 + CONTRIBUTING.md | 38 + CONTRIBUTOR_LICENSE_POLICY.md | 18 + DEPLOYMENT_MODES.md | 18 + DEVELOPMENT.md | 138 + FAQ.md | 60 + FIRST_CONTRIBUTION.md | 45 + GOVERNANCE.md | 30 + LICENSE | 201 + LICENSE-FAQ.md | 22 + MAINTAINERS.md | 24 + PORTFOLIO_CAPABILITIES.md | 19 + PRIVACY_AND_DATA_HANDLING.md | 26 + PROCUREMENT_READINESS.md | 24 + PROJECT_STATUS.md | 114 + PhysOn.sln | 78 + README.md | 190 + RELEASING.md | 38 + REPOSITORY_LAYOUT.md | 41 + ROADMAP.md | 94 + SECURITY.md | 40 + SECURITY_RESPONSE.md | 26 + SHOWCASE.md | 77 + SUPPORT.md | 33 + TRADEMARKS.md | 26 + TRUST_CENTER.md | 27 + deploy/.env.example | 11 + deploy/Caddyfile | 49 + deploy/README.md | 29 + deploy/compose.mvp.yml | 42 + deploy/compose.webapp.yml | 9 + deploy/docker/api.Dockerfile | 25 + deploy/docker/webapp.nginx.conf | 23 + deploy/docker/worker.Dockerfile | 22 + deploy/systemd/vs-messanger-mvp.service | 16 + deploy/systemd/vs-messanger-webapp.service | 16 + docs/README.md | 11 + .../archive/01-product-strategy-definition.md | 217 + docs/archive/README.md | 9 + .../01-backend-platform-architecture.md | 515 +++ .../planning/06-quality-release-and-launch.md | 427 ++ docs/archive/planning/README.md | 14 + .../windows-desktop-client-architecture.md | 489 +++ .../01-visual-interaction-direction.md | 359 ++ .../windows-messenger-planning/README.md | 11 + docs/assets/latest/README.md | 14 + docs/assets/latest/conversation.png | Bin 0 -> 58315 bytes docs/assets/latest/hero-shell.png | Bin 0 -> 109565 bytes docs/assets/latest/onboarding.png | Bin 0 -> 54324 bytes docs/assets/latest/vstalk-web-chat.png | Bin 0 -> 84331 bytes docs/assets/latest/vstalk-web-list.png | Bin 0 -> 58501 bytes docs/assets/latest/vstalk-web-onboarding.png | Bin 0 -> 67294 bytes docs/assets/latest/vstalk-web-saved.png | Bin 0 -> 67950 bytes docs/assets/latest/vstalk-web-search.png | Bin 0 -> 34880 bytes docs/assets/readme/contribution-path.svg | 19 + docs/assets/readme/conversation.png | Bin 0 -> 18236 bytes docs/assets/readme/evaluation-paths.svg | 21 + docs/assets/readme/hero-shell.png | Bin 0 -> 45079 bytes docs/assets/readme/onboarding.png | Bin 0 -> 47857 bytes docs/assets/readme/open-source-surface.svg | 41 + docs/assets/readme/platform-journey.svg | 49 + docs/assets/readme/product-pillars.svg | 54 + docs/assets/readme/public-contract.svg | 26 + docs/assets/readme/release-flow.svg | 55 + docs/assets/readme/system-overview.svg | 84 + docs/repository-surfaces.md | 29 + global.json | 5 + release-assets/.gitignore | 9 + release-assets/README.md | 93 + release-assets/latest/.gitkeep | 0 release-assets/releases/.gitkeep | 1 + release-assets/templates/RELEASE_NOTES.ko.md | 19 + scripts/capture_vstalk_web_screenshots.cjs | 1 + scripts/ci/capture-vstalk-web-screenshots.cjs | 397 ++ scripts/deploy-mvp-stack.sh | 5 + scripts/deploy-webapp.sh | 5 + scripts/deploy/deploy-stack-mvp.sh | 106 + scripts/deploy/deploy-webapp-static.sh | 117 + scripts/prepare-release-assets.sh | 5 + scripts/publish-gitea-release.sh | 5 + scripts/release/release-prepare-assets.sh | 329 ++ scripts/release/release-publish-forge.sh | 190 + scripts/release/release-upload-assets.sh | 100 + scripts/upload-release-assets.sh | 5 + .../Auth/ClaimsPrincipalExtensions.cs | 37 + .../Endpoints/MessengerEndpoints.cs | 310 ++ ...pplicationExceptionMiddlewareExtensions.cs | 28 + src/PhysOn.Api/PhysOn.Api.csproj | 20 + src/PhysOn.Api/Program.cs | 79 + src/PhysOn.Api/Properties/launchSettings.json | 38 + src/PhysOn.Api/appsettings.Development.json | 17 + src/PhysOn.Api/appsettings.json | 32 + .../Abstractions/IAppDbContext.cs | 22 + src/PhysOn.Application/Abstractions/IClock.cs | 6 + .../Abstractions/IRealtimeNotifier.cs | 10 + .../Abstractions/ITokenService.cs | 23 + .../Exceptions/AppException.cs | 25 + .../PhysOn.Application.csproj | 18 + .../Services/MessengerApplicationService.cs | 560 +++ src/PhysOn.Contracts/Auth/AuthContracts.cs | 42 + src/PhysOn.Contracts/Common/ApiEnvelope.cs | 13 + src/PhysOn.Contracts/Common/IdentityDtos.cs | 13 + .../Conversations/ConversationContracts.cs | 50 + src/PhysOn.Contracts/PhysOn.Contracts.csproj | 9 + .../Realtime/RealtimeContracts.cs | 9 + src/PhysOn.Desktop/App.axaml | 16 + src/PhysOn.Desktop/App.axaml.cs | 34 + src/PhysOn.Desktop/Assets/avalonia-logo.ico | Bin 0 -> 175875 bytes src/PhysOn.Desktop/Models/DesktopSession.cs | 8 + .../Models/DesktopWorkspaceLayout.cs | 6 + src/PhysOn.Desktop/PhysOn.Desktop.csproj | 31 + src/PhysOn.Desktop/Program.cs | 23 + .../Services/ConversationWindowManager.cs | 47 + .../Services/IConversationWindowManager.cs | 16 + .../Services/PhysOnApiClient.cs | 127 + .../Services/PhysOnRealtimeClient.cs | 287 ++ src/PhysOn.Desktop/Services/SessionStore.cs | 136 + .../Services/WorkspaceLayoutStore.cs | 51 + src/PhysOn.Desktop/ViewLocator.cs | 37 + .../ViewModels/ConversationRowViewModel.cs | 32 + .../ViewModels/ConversationWindowViewModel.cs | 224 + .../ViewModels/MainWindowViewModel.cs | 1052 +++++ .../ViewModels/MessageRowViewModel.cs | 23 + .../ViewModels/ViewModelBase.cs | 7 + .../Views/ConversationWindow.axaml | 142 + .../Views/ConversationWindow.axaml.cs | 72 + src/PhysOn.Desktop/Views/MainWindow.axaml | 712 +++ src/PhysOn.Desktop/Views/MainWindow.axaml.cs | 87 + src/PhysOn.Desktop/app.manifest | 18 + src/PhysOn.Domain/Accounts/Account.cs | 13 + src/PhysOn.Domain/Accounts/Device.cs | 14 + src/PhysOn.Domain/Accounts/Session.cs | 18 + .../Conversations/Conversation.cs | 13 + .../Conversations/ConversationMember.cs | 16 + .../Conversations/ConversationRole.cs | 7 + .../Conversations/ConversationType.cs | 8 + src/PhysOn.Domain/Invites/Invite.cs | 28 + src/PhysOn.Domain/Messages/Message.cs | 17 + src/PhysOn.Domain/Messages/MessageType.cs | 6 + src/PhysOn.Domain/PhysOn.Domain.csproj | 9 + src/PhysOn.Infrastructure/Auth/JwtOptions.cs | 11 + .../Auth/JwtTokenService.cs | 120 + .../Clock/SystemClock.cs | 8 + .../Persistence/DatabaseInitializer.cs | 76 + .../Persistence/VsMessengerDbContext.cs | 108 + .../PhysOn.Infrastructure.csproj | 31 + .../Realtime/WebSocketConnectionHub.cs | 136 + .../ServiceCollectionExtensions.cs | 112 + src/PhysOn.Web/.gitignore | 25 + src/PhysOn.Web/README.md | 27 + src/PhysOn.Web/eslint.config.js | 28 + src/PhysOn.Web/index.html | 23 + src/PhysOn.Web/package-lock.json | 3811 +++++++++++++++++ src/PhysOn.Web/package.json | 30 + src/PhysOn.Web/public/apple-touch-icon.svg | 5 + src/PhysOn.Web/public/icon.svg | 5 + src/PhysOn.Web/public/manifest.webmanifest | 24 + src/PhysOn.Web/public/mask-icon.svg | 4 + src/PhysOn.Web/public/sw.js | 57 + src/PhysOn.Web/public/vs-mark.svg | 5 + src/PhysOn.Web/src/App.css | 1093 +++++ src/PhysOn.Web/src/App.tsx | 1573 +++++++ src/PhysOn.Web/src/index.css | 71 + src/PhysOn.Web/src/lib/api.ts | 202 + src/PhysOn.Web/src/lib/realtime.ts | 48 + src/PhysOn.Web/src/lib/storage.ts | 98 + src/PhysOn.Web/src/main.tsx | 26 + src/PhysOn.Web/src/types.ts | 156 + src/PhysOn.Web/src/vite-env.d.ts | 1 + src/PhysOn.Web/tsconfig.app.json | 24 + src/PhysOn.Web/tsconfig.json | 7 + src/PhysOn.Web/tsconfig.node.json | 22 + src/PhysOn.Web/vite.config.ts | 30 + src/PhysOn.Worker/PhysOn.Worker.csproj | 18 + src/PhysOn.Worker/Program.cs | 7 + .../Properties/launchSettings.json | 12 + src/PhysOn.Worker/Worker.cs | 23 + .../appsettings.Development.json | 8 + src/PhysOn.Worker/appsettings.json | 8 + .../Infrastructure/PhysOnApiFactory.cs | 41 + .../PhysOn.Api.IntegrationTests.csproj | 28 + .../VerticalSliceTests.cs | 178 + 문서/00-overview-and-decisions.md | 111 + 문서/01-product-strategy-and-mvp.md | 197 + 문서/02-ux-ui-and-brand-direction.md | 200 + 문서/03-windows-client-architecture.md | 235 + 문서/03-보안-프라이버시-운영-리스크-기획안.md | 298 ++ 문서/04-chat-server-vps-architecture.md | 262 ++ 문서/05-security-privacy-and-risk.md | 142 + 문서/06-quality-release-and-launch.md | 229 + 문서/07-roadmap-and-execution-plan.md | 155 + 문서/08-domain-model-and-api-contract.md | 271 ++ 문서/09-korean-ui-writing-system.md | 102 + 문서/10-signup-onboarding-and-auth-policy.md | 82 + 문서/100-roadmap-by-experience-lift.md | 27 + ...t-hierarchy-and-panel-responsibility-spec.md | 20 + ...bar-navigation-vs-filter-separation-rules.md | 19 + ...smart-composer-and-contextual-tool-reveal.md | 22 + ...ssion-recovery-and-last-good-state-policy.md | 21 + ...osition-autoscroll-and-timeline-stability.md | 15 + ...mode-quiet-mode-and-notification-bundling.md | 22 + ...file-context-panel-and-side-surface-rules.md | 22 + ...gue-scorecard-and-heuristic-review-method.md | 23 + ...tus-copy-and-recovery-feedback-guidelines.md | 21 + ...1-kakaotalk-parity-and-superiority-matrix.md | 125 + ...erial-translation-and-navigation-contract.md | 15 + ...old-ux-audit-and-work-simplification-pack.md | 198 + ...urface-expansion-and-critical-qa-proposal.md | 253 ++ ...-technical-operations-release-trust-atlas.md | 344 ++ ...latform-business-and-procurement-strategy.md | 156 + 문서/114-core-differentiation-pillars.md | 107 + 문서/12-first-usable-windows-ux-slice.md | 303 ++ 문서/13-v0.1-api-and-events-contract.md | 752 ++++ .../14-project-background-and-market-context.md | 45 + ...roid-client-and-parallel-release-strategy.md | 112 + .../16-mobile-webapp-product-and-ux-strategy.md | 201 + 문서/17-vstalk-webapp-mvp-and-rollout-plan.md | 120 + 문서/18-white-material-compact-ui-system.md | 59 + ...daptive-window-and-multiwindow-guidelines.md | 31 + ...blic-pattern-benchmark-and-vs-translation.md | 32 + ...1-core-user-journeys-and-task-time-budget.md | 299 ++ 문서/22-work-communication-ux-playbook.md | 228 + 문서/23-friendly-conversation-ux-playbook.md | 204 + .../24-search-triage-and-knowledge-retrieval.md | 207 + ...25-draft-recovery-and-message-reliability.md | 162 + ...6-notification-focus-and-attention-policy.md | 163 + ...oss-device-handoff-and-session-continuity.md | 196 + 문서/28-file-link-media-usage-model.md | 202 + ...desktop-productivity-and-multiwindow-spec.md | 212 + ...bile-web-and-android-parallel-ux-contract.md | 174 + ...-user-review-log-and-experience-scorecard.md | 163 + .../32-metrics-experiments-and-release-gates.md | 157 + 문서/33-platform-capability-matrix.md | 110 + 문서/34-current-limitations-and-doc-drift.md | 112 + .../35-live-user-review-and-priority-backlog.md | 172 + .../36-conversation-information-architecture.md | 112 + .../37-composer-reply-reaction-forward-spec.md | 95 + ...ct-invite-identity-and-relationship-model.md | 74 + ...min-operations-support-and-trust-playbook.md | 74 + ...40-design-qa-checklists-and-review-rubric.md | 69 + ...ding-empty-state-and-first-week-retention.md | 78 + ...-automation-shortcuts-and-command-surface.md | 42 + ...ty-readability-and-low-fatigue-guidelines.md | 36 + ...nce-perceived-speed-and-resilience-design.md | 34 + ...screenshots-and-public-surface-guidelines.md | 40 + ...eeting-mode-approvals-and-quick-decisions.md | 32 + ...-language-and-korean-microcopy-principles.md | 40 + ...-persona-scenarios-and-success-narratives.md | 38 + 문서/49-workspace-presets-and-focus-modes.md | 33 + ...tes-self-chat-and-personal-knowledge-flow.md | 30 + ...ssage-states-errors-and-recovery-language.md | 37 + ...-sharing-invites-growth-and-team-adoption.md | 34 + ...bile-layout-breakpoint-and-safe-area-spec.md | 31 + ...release-surface-and-apk-distribution-plan.md | 23 + ...ws-build-artifact-and-screenshot-workflow.md | 23 + 문서/56-qa-scenarios-by-user-type.md | 45 + ...security-trust-surface-and-user-education.md | 29 + ...58-search-ranking-and-result-presentation.md | 36 + ...-roadmap-by-quarter-and-parity-thresholds.md | 41 + ...-source-community-issue-triage-guidelines.md | 32 + ...-data-retention-privacy-and-user-controls.md | 32 + .../62-competitive-differentiation-by-task.md | 32 + 문서/63-inbox-triage-priority-views.md | 43 + ...er-journey-review-framework-and-qa-topics.md | 445 ++ ...k-and-lifestyle-ux-capability-translation.md | 120 + 문서/64-convenience-priority-stack.md | 264 ++ ...4-low-fatigue-work-messenger-ui-expansion.md | 248 ++ 문서/64-reply-later-and-follow-up-flow.md | 35 + ...65-message-reminders-snooze-and-deadlines.md | 29 + 문서/66-unread-clearing-and-catchup-mode.md | 29 + ...ncement-broadcast-and-read-receipt-policy.md | 29 + ...68-inline-quick-actions-and-command-chips.md | 29 + ...rch-zero-state-recents-and-saved-searches.md | 30 + ...attachment-preview-collection-and-handoff.md | 24 + ...ing-briefing-before-during-and-after-flow.md | 29 + ...ecision-log-approvals-and-resolution-flow.md | 26 + ...3-task-handoff-and-responsibility-markers.md | 26 + 문서/74-shared-links-reference-panels.md | 21 + ...ebar-panels-pinned-slots-and-context-rail.md | 23 + .../76-quick-phrases-templates-and-snippets.md | 27 + ...ification-batching-digest-and-shift-modes.md | 29 + 문서/78-offline-mode-sync-and-outbox-rules.md | 27 + ...artup-resume-and-last-context-restoration.md | 22 + 문서/80-desktop-layout-recipes.md | 23 + .../81-mobile-one-thumb-navigation-patterns.md | 28 + 문서/82-pwa-install-and-reentry.md | 21 + 문서/83-profile-presence-and-status-card.md | 23 + 문서/84-boundaries-block-mute-and-safety.md | 23 + 문서/85-bookmarks-tags-and-collections.md | 26 + .../86-empty-loading-and-error-trust-states.md | 22 + ...7-reading-rhythm-density-and-scannability.md | 22 + ...essage-scheduling-delayed-send-and-unsend.md | 21 + ...current-product-mobile-web-review-2026-04.md | 49 + ...rrent-product-work-journey-review-2026-04.md | 34 + ...t-product-friendly-journey-review-2026-04.md | 21 + 문서/92-adoption-support-and-trust-kit.md | 19 + ...93-team-rollout-and-first-week-checklists.md | 27 + .../94-user-interview-script-and-study-guide.md | 23 + 문서/95-beta-feedback-and-research-cadence.md | 22 + .../96-experience-gap-implementation-backlog.md | 31 + .../97-anti-patterns-and-what-not-to-build.md | 22 + ...ce-trust-surface-and-status-communication.md | 22 + 문서/99-issue-triage-by-user-pain.md | 23 + 문서/README.md | 40 + 문서/atlas/01-work-inbox/01-triage-modes.md | 43 + 문서/atlas/01-work-inbox/02-role-queues.md | 43 + .../atlas/01-work-inbox/03-urgent-important.md | 43 + .../atlas/01-work-inbox/04-day-start-panel.md | 43 + 문서/atlas/01-work-inbox/05-catch-up-plan.md | 43 + 문서/atlas/01-work-inbox/06-shift-handover.md | 43 + 문서/atlas/01-work-inbox/07-unread-debt.md | 43 + 문서/atlas/01-work-inbox/08-approval-queue.md | 43 + .../atlas/01-work-inbox/09-stakeholder-ring.md | 43 + .../atlas/01-work-inbox/10-decision-extract.md | 43 + .../atlas/01-work-inbox/11-status-followup.md | 43 + 문서/atlas/01-work-inbox/12-quiet-work.md | 43 + 문서/atlas/01-work-inbox/13-work-bookmarks.md | 43 + .../atlas/01-work-inbox/14-starred-as-tasks.md | 43 + 문서/atlas/01-work-inbox/15-daily-brief.md | 43 + 문서/atlas/01-work-inbox/16-weekly-digest.md | 43 + 문서/atlas/01-work-inbox/17-archive-recall.md | 43 + 문서/atlas/01-work-inbox/18-sla-hints.md | 43 + .../atlas/01-work-inbox/19-ownership-chips.md | 43 + .../atlas/01-work-inbox/20-escalation-ladder.md | 43 + 문서/atlas/01-work-inbox/README.md | 34 + .../01-global-search-home.md | 43 + .../02-search-knowledge/02-query-grammar.md | 43 + .../03-recents-and-history.md | 43 + .../02-search-knowledge/04-work-rediscovery.md | 43 + .../05-friendly-rediscovery.md | 43 + .../06-people-rooms-files.md | 43 + .../07-zero-result-recovery.md | 43 + .../02-search-knowledge/08-saved-searches.md | 43 + .../02-search-knowledge/09-search-filters.md | 43 + .../10-ranking-explainability.md | 43 + .../11-link-and-file-recall.md | 43 + .../12-search-from-message.md | 43 + .../13-keyword-suggestions.md | 43 + .../02-search-knowledge/14-time-range-pivots.md | 43 + .../02-search-knowledge/15-search-shortcuts.md | 43 + .../16-search-quality-signals.md | 43 + .../02-search-knowledge/17-knowledge-panels.md | 43 + .../18-search-empty-states.md | 43 + .../02-search-knowledge/19-search-privacy.md | 43 + .../20-search-release-gates.md | 43 + 문서/atlas/02-search-knowledge/README.md | 34 + .../03-meetings-approvals/01-agenda-room.md | 43 + .../03-meetings-approvals/02-brief-before.md | 43 + .../03-meetings-approvals/03-live-decisions.md | 43 + .../03-meetings-approvals/04-action-items.md | 43 + .../05-approval-requests.md | 43 + .../03-meetings-approvals/06-summary-after.md | 43 + .../07-followup-reminders.md | 43 + .../08-participant-state.md | 43 + .../03-meetings-approvals/09-presenter-mode.md | 43 + .../03-meetings-approvals/10-minute-selfchat.md | 43 + .../03-meetings-approvals/11-quick-votes.md | 43 + .../12-task-from-message.md | 43 + .../03-meetings-approvals/13-due-date-flow.md | 43 + .../03-meetings-approvals/14-meeting-pin.md | 43 + .../03-meetings-approvals/15-doc-sharing.md | 43 + .../03-meetings-approvals/16-search-presets.md | 43 + .../03-meetings-approvals/17-handoff-after.md | 43 + .../03-meetings-approvals/18-async-board.md | 43 + .../19-approval-escalation.md | 43 + .../20-fatigue-controls.md | 43 + 문서/atlas/03-meetings-approvals/README.md | 34 + .../04-friendly-conversation/01-light-entry.md | 43 + .../02-low-pressure-reply.md | 43 + .../03-reaction-rhythm.md | 43 + .../04-sticker-gap-policy.md | 43 + .../05-friend-group-noise.md | 43 + .../06-private-boundaries.md | 43 + .../07-birthdays-events.md | 43 + .../08-photo-first-sharing.md | 43 + .../04-friendly-conversation/09-casual-plans.md | 43 + .../10-voice-note-future.md | 43 + .../04-friendly-conversation/11-mood-status.md | 43 + .../12-lightbookmarks.md | 43 + .../13-context-memory.md | 43 + .../14-warm-empty-states.md | 43 + .../15-social-reentry.md | 43 + .../16-soft-notifications.md | 43 + .../17-close-friends-surface.md | 43 + .../18-fun-without-noise.md | 43 + .../19-privacy-in-friend-chat.md | 43 + .../20-friendly-review-rubric.md | 43 + 문서/atlas/04-friendly-conversation/README.md | 34 + .../01-resizable-shell.md | 43 + .../02-multiwindow-rules.md | 43 + .../05-desktop-productivity/03-split-panels.md | 43 + .../05-desktop-productivity/04-popout-chat.md | 43 + .../05-keyboard-first.md | 43 + .../06-command-surface.md | 43 + .../07-search-overlay.md | 43 + .../05-desktop-productivity/08-context-rail.md | 43 + .../09-attachment-sidepanel.md | 43 + .../05-desktop-productivity/10-notes-column.md | 43 + .../11-compact-density.md | 43 + .../05-desktop-productivity/12-window-memory.md | 43 + .../13-monitor-aware-layout.md | 43 + .../14-desktop-notifications.md | 43 + .../15-clipboard-accelerators.md | 43 + .../16-desktop-offline-cues.md | 43 + .../17-window-reentry.md | 43 + .../18-presentation-quiet.md | 43 + .../19-admin-power-user.md | 43 + .../20-desktop-release-checks.md | 43 + 문서/atlas/05-desktop-productivity/README.md | 34 + .../06-mobile-ergonomics/01-thumb-zones.md | 43 + .../02-bottom-nav-purpose.md | 43 + .../06-mobile-ergonomics/03-list-density.md | 43 + .../06-mobile-ergonomics/04-input-ergonomics.md | 43 + .../06-mobile-ergonomics/05-search-entry.md | 43 + .../06-mobile-ergonomics/06-saved-patterns.md | 43 + .../06-mobile-ergonomics/07-me-space-minimal.md | 43 + .../08-refresh-vs-action.md | 43 + .../atlas/06-mobile-ergonomics/09-safe-area.md | 43 + .../06-mobile-ergonomics/10-scroll-preserve.md | 43 + .../06-mobile-ergonomics/11-keyboard-open.md | 43 + .../06-mobile-ergonomics/12-gesture-back.md | 43 + .../13-context-restraint.md | 43 + .../atlas/06-mobile-ergonomics/14-small-copy.md | 43 + .../06-mobile-ergonomics/15-long-session.md | 43 + .../06-mobile-ergonomics/16-status-chips.md | 43 + .../17-attachment-future.md | 43 + .../06-mobile-ergonomics/18-offline-cues.md | 43 + .../06-mobile-ergonomics/19-install-cue.md | 43 + .../06-mobile-ergonomics/20-battery-courtesy.md | 43 + 문서/atlas/06-mobile-ergonomics/README.md | 34 + .../07-trust-recovery/01-trust-entry-copy.md | 43 + .../07-trust-recovery/02-session-expiry.md | 43 + .../07-trust-recovery/03-last-good-state.md | 43 + .../07-trust-recovery/04-reconnect-language.md | 43 + .../05-auth-error-ownership.md | 43 + .../07-trust-recovery/06-device-list-trust.md | 43 + .../07-trust-recovery/07-remote-signout.md | 43 + .../08-data-safety-phrasing.md | 43 + .../07-trust-recovery/09-empty-vs-error.md | 43 + .../07-trust-recovery/10-loading-honesty.md | 43 + .../07-trust-recovery/11-outbox-recovery.md | 43 + .../07-trust-recovery/12-message-failure.md | 43 + .../07-trust-recovery/13-version-mismatch.md | 43 + .../07-trust-recovery/14-maintenance-notice.md | 43 + .../07-trust-recovery/15-download-integrity.md | 43 + .../16-public-status-surface.md | 43 + .../atlas/07-trust-recovery/17-support-route.md | 43 + .../07-trust-recovery/18-privacy-controls.md | 43 + .../07-trust-recovery/19-recovery-metrics.md | 43 + .../07-trust-recovery/20-trust-release-gate.md | 43 + 문서/atlas/07-trust-recovery/README.md | 34 + .../01-notification-ladder.md | 43 + .../08-notifications-presence/02-quiet-mode.md | 43 + .../08-notifications-presence/03-shift-mode.md | 43 + .../08-notifications-presence/04-digest-mode.md | 43 + .../05-mention-priority.md | 43 + .../06-presence-states.md | 43 + .../07-workload-status.md | 43 + .../08-notifications-presence/08-away-back.md | 43 + .../09-summary-copy.md | 43 + .../10-per-thread-control.md | 43 + .../11-pinned-bundles.md | 43 + .../12-calendar-silence.md | 43 + .../13-friend-work-split.md | 43 + .../14-lockscreen-privacy.md | 43 + .../15-cross-device-noti.md | 43 + .../08-notifications-presence/16-after-muted.md | 43 + .../17-fatigue-score.md | 43 + .../18-badge-semantics.md | 43 + .../19-urgency-levels.md | 43 + .../20-noti-analytics.md | 43 + .../atlas/08-notifications-presence/README.md | 34 + .../01-first-week-rollout.md | 43 + .../09-adoption-rollout/02-role-onboarding.md | 43 + .../09-adoption-rollout/03-invite-campaigns.md | 43 + .../04-migration-checklist.md | 43 + .../09-adoption-rollout/05-office-starter.md | 43 + .../09-adoption-rollout/06-lead-starter.md | 43 + .../09-adoption-rollout/07-support-macros.md | 43 + .../09-adoption-rollout/08-adoption-metrics.md | 43 + .../09-adoption-rollout/09-champion-program.md | 43 + .../09-adoption-rollout/10-training-deck.md | 43 + .../11-release-notes-users.md | 43 + 문서/atlas/09-adoption-rollout/12-team-faq.md | 43 + .../09-adoption-rollout/13-support-triage.md | 43 + .../09-adoption-rollout/14-change-management.md | 43 + .../09-adoption-rollout/15-reactivation.md | 43 + .../16-onboarding-experiments.md | 43 + .../09-adoption-rollout/17-trust-messages.md | 43 + .../09-adoption-rollout/18-beta-feedback.md | 43 + .../09-adoption-rollout/19-community-funnel.md | 43 + .../09-adoption-rollout/20-persona-education.md | 43 + 문서/atlas/09-adoption-rollout/README.md | 34 + .../atlas/10-release-ops/01-version-manifest.md | 43 + .../02-windows-release-pipeline.md | 43 + .../10-release-ops/03-web-release-pipeline.md | 43 + .../04-android-release-pipeline.md | 43 + .../10-release-ops/05-download-host-routing.md | 43 + .../10-release-ops/06-release-notes-style.md | 43 + .../10-release-ops/07-screenshot-discipline.md | 43 + .../10-release-ops/08-checksum-signature.md | 43 + .../10-release-ops/09-rollback-playbook.md | 43 + .../10-release-ops/10-observability-surface.md | 43 + 문서/atlas/10-release-ops/11-backup-drills.md | 43 + .../atlas/10-release-ops/12-alert-thresholds.md | 43 + .../atlas/10-release-ops/13-secret-handling.md | 43 + .../10-release-ops/14-public-status-page.md | 43 + .../10-release-ops/15-release-truthfulness.md | 43 + .../10-release-ops/16-artifact-retention.md | 43 + .../10-release-ops/17-gitea-release-flow.md | 43 + .../atlas/10-release-ops/18-os-route-design.md | 43 + .../10-release-ops/19-release-regression.md | 43 + .../10-release-ops/20-ops-readiness-gate.md | 43 + 문서/atlas/10-release-ops/README.md | 34 + .../11-qa-reviews/01-current-mobile-overall.md | 44 + .../02-mobile-first-three-minutes.md | 43 + .../03-mobile-navigation-clarity.md | 43 + .../04-mobile-first-room-emptiness.md | 48 + .../11-qa-reviews/05-mobile-search-depth.md | 48 + .../06-mobile-saved-area-credibility.md | 48 + .../07-mobile-session-trust-copy.md | 48 + .../08-mobile-empty-state-surface.md | 43 + .../09-windows-alpha-first-impression.md | 43 + .../10-windows-resize-and-density.md | 43 + .../11-windows-multiwindow-priority.md | 43 + .../12-desktop-search-readiness.md | 43 + .../13-cross-platform-consistency.md | 43 + .../14-onboarding-failure-taxonomy.md | 43 + .../15-session-recovery-failure-matrix.md | 43 + .../11-qa-reviews/16-search-zero-result-qa.md | 43 + .../11-qa-reviews/17-critical-gaps-top10.md | 48 + .../11-qa-reviews/18-skeptical-user-memo.md | 49 + .../11-qa-reviews/19-release-surface-honesty.md | 43 + .../20-version-regression-tracker.md | 43 + 문서/atlas/11-qa-reviews/README.md | 34 + .../atlas/12-android-parallel/01-android-ia.md | 43 + .../atlas/12-android-parallel/02-android-nav.md | 43 + .../12-android-parallel/03-android-density.md | 43 + .../12-android-parallel/04-android-composer.md | 43 + .../05-android-notification-bridge.md | 43 + .../06-android-work-profile.md | 43 + .../12-android-parallel/07-android-storage.md | 43 + .../08-android-apk-policy.md | 43 + .../09-android-install-onboarding.md | 43 + .../12-android-parallel/10-android-handoff.md | 43 + .../12-android-parallel/11-android-material3.md | 43 + .../12-android-parallel/12-android-tablet.md | 43 + .../13-android-attachments.md | 43 + .../14-android-permissions.md | 43 + .../15-android-battery-data.md | 43 + .../12-android-parallel/16-android-search.md | 43 + .../17-android-saved-area.md | 43 + .../12-android-parallel/18-android-offline.md | 43 + .../19-android-trust-privacy.md | 43 + .../20-android-parity-roadmap.md | 43 + 문서/atlas/12-android-parallel/README.md | 34 + 문서/atlas/README.md | 23 + 572 files changed, 41689 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/ux_review.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-portable.yml create mode 100644 .gitignore create mode 100644 ARCHITECTURE.md create mode 100644 BACKGROUND.md create mode 100644 BRANCHING_STRATEGY.md create mode 100644 BUSINESS_MODEL.md create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 COMMUNITY.md create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTOR_LICENSE_POLICY.md create mode 100644 DEPLOYMENT_MODES.md create mode 100644 DEVELOPMENT.md create mode 100644 FAQ.md create mode 100644 FIRST_CONTRIBUTION.md create mode 100644 GOVERNANCE.md create mode 100644 LICENSE create mode 100644 LICENSE-FAQ.md create mode 100644 MAINTAINERS.md create mode 100644 PORTFOLIO_CAPABILITIES.md create mode 100644 PRIVACY_AND_DATA_HANDLING.md create mode 100644 PROCUREMENT_READINESS.md create mode 100644 PROJECT_STATUS.md create mode 100644 PhysOn.sln create mode 100644 README.md create mode 100644 RELEASING.md create mode 100644 REPOSITORY_LAYOUT.md create mode 100644 ROADMAP.md create mode 100644 SECURITY.md create mode 100644 SECURITY_RESPONSE.md create mode 100644 SHOWCASE.md create mode 100644 SUPPORT.md create mode 100644 TRADEMARKS.md create mode 100644 TRUST_CENTER.md create mode 100644 deploy/.env.example create mode 100644 deploy/Caddyfile create mode 100644 deploy/README.md create mode 100644 deploy/compose.mvp.yml create mode 100644 deploy/compose.webapp.yml create mode 100644 deploy/docker/api.Dockerfile create mode 100644 deploy/docker/webapp.nginx.conf create mode 100644 deploy/docker/worker.Dockerfile create mode 100644 deploy/systemd/vs-messanger-mvp.service create mode 100644 deploy/systemd/vs-messanger-webapp.service create mode 100644 docs/README.md create mode 100644 docs/archive/01-product-strategy-definition.md create mode 100644 docs/archive/README.md create mode 100644 docs/archive/planning/01-backend-platform-architecture.md create mode 100644 docs/archive/planning/06-quality-release-and-launch.md create mode 100644 docs/archive/planning/README.md create mode 100644 docs/archive/planning/windows-desktop-client-architecture.md create mode 100644 docs/archive/windows-messenger-planning/01-visual-interaction-direction.md create mode 100644 docs/archive/windows-messenger-planning/README.md create mode 100644 docs/assets/latest/README.md create mode 100644 docs/assets/latest/conversation.png create mode 100644 docs/assets/latest/hero-shell.png create mode 100644 docs/assets/latest/onboarding.png create mode 100644 docs/assets/latest/vstalk-web-chat.png create mode 100644 docs/assets/latest/vstalk-web-list.png create mode 100644 docs/assets/latest/vstalk-web-onboarding.png create mode 100644 docs/assets/latest/vstalk-web-saved.png create mode 100644 docs/assets/latest/vstalk-web-search.png create mode 100644 docs/assets/readme/contribution-path.svg create mode 100644 docs/assets/readme/conversation.png create mode 100644 docs/assets/readme/evaluation-paths.svg create mode 100644 docs/assets/readme/hero-shell.png create mode 100644 docs/assets/readme/onboarding.png create mode 100644 docs/assets/readme/open-source-surface.svg create mode 100644 docs/assets/readme/platform-journey.svg create mode 100644 docs/assets/readme/product-pillars.svg create mode 100644 docs/assets/readme/public-contract.svg create mode 100644 docs/assets/readme/release-flow.svg create mode 100644 docs/assets/readme/system-overview.svg create mode 100644 docs/repository-surfaces.md create mode 100644 global.json create mode 100644 release-assets/.gitignore create mode 100644 release-assets/README.md create mode 100644 release-assets/latest/.gitkeep create mode 100644 release-assets/releases/.gitkeep create mode 100644 release-assets/templates/RELEASE_NOTES.ko.md create mode 100644 scripts/capture_vstalk_web_screenshots.cjs create mode 100644 scripts/ci/capture-vstalk-web-screenshots.cjs create mode 100755 scripts/deploy-mvp-stack.sh create mode 100755 scripts/deploy-webapp.sh create mode 100755 scripts/deploy/deploy-stack-mvp.sh create mode 100755 scripts/deploy/deploy-webapp-static.sh create mode 100755 scripts/prepare-release-assets.sh create mode 100755 scripts/publish-gitea-release.sh create mode 100755 scripts/release/release-prepare-assets.sh create mode 100755 scripts/release/release-publish-forge.sh create mode 100755 scripts/release/release-upload-assets.sh create mode 100755 scripts/upload-release-assets.sh create mode 100644 src/PhysOn.Api/Auth/ClaimsPrincipalExtensions.cs create mode 100644 src/PhysOn.Api/Endpoints/MessengerEndpoints.cs create mode 100644 src/PhysOn.Api/Infrastructure/ApplicationExceptionMiddlewareExtensions.cs create mode 100644 src/PhysOn.Api/PhysOn.Api.csproj create mode 100644 src/PhysOn.Api/Program.cs create mode 100644 src/PhysOn.Api/Properties/launchSettings.json create mode 100644 src/PhysOn.Api/appsettings.Development.json create mode 100644 src/PhysOn.Api/appsettings.json create mode 100644 src/PhysOn.Application/Abstractions/IAppDbContext.cs create mode 100644 src/PhysOn.Application/Abstractions/IClock.cs create mode 100644 src/PhysOn.Application/Abstractions/IRealtimeNotifier.cs create mode 100644 src/PhysOn.Application/Abstractions/ITokenService.cs create mode 100644 src/PhysOn.Application/Exceptions/AppException.cs create mode 100644 src/PhysOn.Application/PhysOn.Application.csproj create mode 100644 src/PhysOn.Application/Services/MessengerApplicationService.cs create mode 100644 src/PhysOn.Contracts/Auth/AuthContracts.cs create mode 100644 src/PhysOn.Contracts/Common/ApiEnvelope.cs create mode 100644 src/PhysOn.Contracts/Common/IdentityDtos.cs create mode 100644 src/PhysOn.Contracts/Conversations/ConversationContracts.cs create mode 100644 src/PhysOn.Contracts/PhysOn.Contracts.csproj create mode 100644 src/PhysOn.Contracts/Realtime/RealtimeContracts.cs create mode 100644 src/PhysOn.Desktop/App.axaml create mode 100644 src/PhysOn.Desktop/App.axaml.cs create mode 100644 src/PhysOn.Desktop/Assets/avalonia-logo.ico create mode 100644 src/PhysOn.Desktop/Models/DesktopSession.cs create mode 100644 src/PhysOn.Desktop/Models/DesktopWorkspaceLayout.cs create mode 100644 src/PhysOn.Desktop/PhysOn.Desktop.csproj create mode 100644 src/PhysOn.Desktop/Program.cs create mode 100644 src/PhysOn.Desktop/Services/ConversationWindowManager.cs create mode 100644 src/PhysOn.Desktop/Services/IConversationWindowManager.cs create mode 100644 src/PhysOn.Desktop/Services/PhysOnApiClient.cs create mode 100644 src/PhysOn.Desktop/Services/PhysOnRealtimeClient.cs create mode 100644 src/PhysOn.Desktop/Services/SessionStore.cs create mode 100644 src/PhysOn.Desktop/Services/WorkspaceLayoutStore.cs create mode 100644 src/PhysOn.Desktop/ViewLocator.cs create mode 100644 src/PhysOn.Desktop/ViewModels/ConversationRowViewModel.cs create mode 100644 src/PhysOn.Desktop/ViewModels/ConversationWindowViewModel.cs create mode 100644 src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs create mode 100644 src/PhysOn.Desktop/ViewModels/MessageRowViewModel.cs create mode 100644 src/PhysOn.Desktop/ViewModels/ViewModelBase.cs create mode 100644 src/PhysOn.Desktop/Views/ConversationWindow.axaml create mode 100644 src/PhysOn.Desktop/Views/ConversationWindow.axaml.cs create mode 100644 src/PhysOn.Desktop/Views/MainWindow.axaml create mode 100644 src/PhysOn.Desktop/Views/MainWindow.axaml.cs create mode 100644 src/PhysOn.Desktop/app.manifest create mode 100644 src/PhysOn.Domain/Accounts/Account.cs create mode 100644 src/PhysOn.Domain/Accounts/Device.cs create mode 100644 src/PhysOn.Domain/Accounts/Session.cs create mode 100644 src/PhysOn.Domain/Conversations/Conversation.cs create mode 100644 src/PhysOn.Domain/Conversations/ConversationMember.cs create mode 100644 src/PhysOn.Domain/Conversations/ConversationRole.cs create mode 100644 src/PhysOn.Domain/Conversations/ConversationType.cs create mode 100644 src/PhysOn.Domain/Invites/Invite.cs create mode 100644 src/PhysOn.Domain/Messages/Message.cs create mode 100644 src/PhysOn.Domain/Messages/MessageType.cs create mode 100644 src/PhysOn.Domain/PhysOn.Domain.csproj create mode 100644 src/PhysOn.Infrastructure/Auth/JwtOptions.cs create mode 100644 src/PhysOn.Infrastructure/Auth/JwtTokenService.cs create mode 100644 src/PhysOn.Infrastructure/Clock/SystemClock.cs create mode 100644 src/PhysOn.Infrastructure/Persistence/DatabaseInitializer.cs create mode 100644 src/PhysOn.Infrastructure/Persistence/VsMessengerDbContext.cs create mode 100644 src/PhysOn.Infrastructure/PhysOn.Infrastructure.csproj create mode 100644 src/PhysOn.Infrastructure/Realtime/WebSocketConnectionHub.cs create mode 100644 src/PhysOn.Infrastructure/ServiceCollectionExtensions.cs create mode 100644 src/PhysOn.Web/.gitignore create mode 100644 src/PhysOn.Web/README.md create mode 100644 src/PhysOn.Web/eslint.config.js create mode 100644 src/PhysOn.Web/index.html create mode 100644 src/PhysOn.Web/package-lock.json create mode 100644 src/PhysOn.Web/package.json create mode 100644 src/PhysOn.Web/public/apple-touch-icon.svg create mode 100644 src/PhysOn.Web/public/icon.svg create mode 100644 src/PhysOn.Web/public/manifest.webmanifest create mode 100644 src/PhysOn.Web/public/mask-icon.svg create mode 100644 src/PhysOn.Web/public/sw.js create mode 100644 src/PhysOn.Web/public/vs-mark.svg create mode 100644 src/PhysOn.Web/src/App.css create mode 100644 src/PhysOn.Web/src/App.tsx create mode 100644 src/PhysOn.Web/src/index.css create mode 100644 src/PhysOn.Web/src/lib/api.ts create mode 100644 src/PhysOn.Web/src/lib/realtime.ts create mode 100644 src/PhysOn.Web/src/lib/storage.ts create mode 100644 src/PhysOn.Web/src/main.tsx create mode 100644 src/PhysOn.Web/src/types.ts create mode 100644 src/PhysOn.Web/src/vite-env.d.ts create mode 100644 src/PhysOn.Web/tsconfig.app.json create mode 100644 src/PhysOn.Web/tsconfig.json create mode 100644 src/PhysOn.Web/tsconfig.node.json create mode 100644 src/PhysOn.Web/vite.config.ts create mode 100644 src/PhysOn.Worker/PhysOn.Worker.csproj create mode 100644 src/PhysOn.Worker/Program.cs create mode 100644 src/PhysOn.Worker/Properties/launchSettings.json create mode 100644 src/PhysOn.Worker/Worker.cs create mode 100644 src/PhysOn.Worker/appsettings.Development.json create mode 100644 src/PhysOn.Worker/appsettings.json create mode 100644 tests/PhysOn.Api.IntegrationTests/Infrastructure/PhysOnApiFactory.cs create mode 100644 tests/PhysOn.Api.IntegrationTests/PhysOn.Api.IntegrationTests.csproj create mode 100644 tests/PhysOn.Api.IntegrationTests/VerticalSliceTests.cs create mode 100644 문서/00-overview-and-decisions.md create mode 100644 문서/01-product-strategy-and-mvp.md create mode 100644 문서/02-ux-ui-and-brand-direction.md create mode 100644 문서/03-windows-client-architecture.md create mode 100644 문서/03-보안-프라이버시-운영-리스크-기획안.md create mode 100644 문서/04-chat-server-vps-architecture.md create mode 100644 문서/05-security-privacy-and-risk.md create mode 100644 문서/06-quality-release-and-launch.md create mode 100644 문서/07-roadmap-and-execution-plan.md create mode 100644 문서/08-domain-model-and-api-contract.md create mode 100644 문서/09-korean-ui-writing-system.md create mode 100644 문서/10-signup-onboarding-and-auth-policy.md create mode 100644 문서/100-roadmap-by-experience-lift.md create mode 100644 문서/101-layout-hierarchy-and-panel-responsibility-spec.md create mode 100644 문서/102-bottom-bar-navigation-vs-filter-separation-rules.md create mode 100644 문서/103-smart-composer-and-contextual-tool-reveal.md create mode 100644 문서/104-session-recovery-and-last-good-state-policy.md create mode 100644 문서/105-reading-position-autoscroll-and-timeline-stability.md create mode 100644 문서/106-focus-mode-quiet-mode-and-notification-bundling.md create mode 100644 문서/107-link-file-context-panel-and-side-surface-rules.md create mode 100644 문서/108-user-fatigue-scorecard-and-heuristic-review-method.md create mode 100644 문서/109-trustful-status-copy-and-recovery-feedback-guidelines.md create mode 100644 문서/11-kakaotalk-parity-and-superiority-matrix.md create mode 100644 문서/110-android-material-translation-and-navigation-contract.md create mode 100644 문서/111-cold-ux-audit-and-work-simplification-pack.md create mode 100644 문서/112-review-surface-expansion-and-critical-qa-proposal.md create mode 100644 문서/112-technical-operations-release-trust-atlas.md create mode 100644 문서/113-open-core-platform-business-and-procurement-strategy.md create mode 100644 문서/114-core-differentiation-pillars.md create mode 100644 문서/12-first-usable-windows-ux-slice.md create mode 100644 문서/13-v0.1-api-and-events-contract.md create mode 100644 문서/14-project-background-and-market-context.md create mode 100644 문서/15-android-client-and-parallel-release-strategy.md create mode 100644 문서/16-mobile-webapp-product-and-ux-strategy.md create mode 100644 문서/17-vstalk-webapp-mvp-and-rollout-plan.md create mode 100644 문서/18-white-material-compact-ui-system.md create mode 100644 문서/19-desktop-adaptive-window-and-multiwindow-guidelines.md create mode 100644 문서/20-kakao-public-pattern-benchmark-and-vs-translation.md create mode 100644 문서/21-core-user-journeys-and-task-time-budget.md create mode 100644 문서/22-work-communication-ux-playbook.md create mode 100644 문서/23-friendly-conversation-ux-playbook.md create mode 100644 문서/24-search-triage-and-knowledge-retrieval.md create mode 100644 문서/25-draft-recovery-and-message-reliability.md create mode 100644 문서/26-notification-focus-and-attention-policy.md create mode 100644 문서/27-cross-device-handoff-and-session-continuity.md create mode 100644 문서/28-file-link-media-usage-model.md create mode 100644 문서/29-desktop-productivity-and-multiwindow-spec.md create mode 100644 문서/30-mobile-web-and-android-parallel-ux-contract.md create mode 100644 문서/31-user-review-log-and-experience-scorecard.md create mode 100644 문서/32-metrics-experiments-and-release-gates.md create mode 100644 문서/33-platform-capability-matrix.md create mode 100644 문서/34-current-limitations-and-doc-drift.md create mode 100644 문서/35-live-user-review-and-priority-backlog.md create mode 100644 문서/36-conversation-information-architecture.md create mode 100644 문서/37-composer-reply-reaction-forward-spec.md create mode 100644 문서/38-contact-invite-identity-and-relationship-model.md create mode 100644 문서/39-admin-operations-support-and-trust-playbook.md create mode 100644 문서/40-design-qa-checklists-and-review-rubric.md create mode 100644 문서/41-onboarding-empty-state-and-first-week-retention.md create mode 100644 문서/42-workflow-automation-shortcuts-and-command-surface.md create mode 100644 문서/43-accessibility-readability-and-low-fatigue-guidelines.md create mode 100644 문서/44-performance-perceived-speed-and-resilience-design.md create mode 100644 문서/45-release-storytelling-screenshots-and-public-surface-guidelines.md create mode 100644 문서/46-meeting-mode-approvals-and-quick-decisions.md create mode 100644 문서/47-trust-language-and-korean-microcopy-principles.md create mode 100644 문서/48-persona-scenarios-and-success-narratives.md create mode 100644 문서/49-workspace-presets-and-focus-modes.md create mode 100644 문서/50-notes-self-chat-and-personal-knowledge-flow.md create mode 100644 문서/51-message-states-errors-and-recovery-language.md create mode 100644 문서/52-sharing-invites-growth-and-team-adoption.md create mode 100644 문서/53-mobile-layout-breakpoint-and-safe-area-spec.md create mode 100644 문서/54-android-release-surface-and-apk-distribution-plan.md create mode 100644 문서/55-windows-build-artifact-and-screenshot-workflow.md create mode 100644 문서/56-qa-scenarios-by-user-type.md create mode 100644 문서/57-security-trust-surface-and-user-education.md create mode 100644 문서/58-search-ranking-and-result-presentation.md create mode 100644 문서/59-roadmap-by-quarter-and-parity-thresholds.md create mode 100644 문서/60-open-source-community-issue-triage-guidelines.md create mode 100644 문서/61-data-retention-privacy-and-user-controls.md create mode 100644 문서/62-competitive-differentiation-by-task.md create mode 100644 문서/63-inbox-triage-priority-views.md create mode 100644 문서/63-user-journey-review-framework-and-qa-topics.md create mode 100644 문서/63-work-and-lifestyle-ux-capability-translation.md create mode 100644 문서/64-convenience-priority-stack.md create mode 100644 문서/64-low-fatigue-work-messenger-ui-expansion.md create mode 100644 문서/64-reply-later-and-follow-up-flow.md create mode 100644 문서/65-message-reminders-snooze-and-deadlines.md create mode 100644 문서/66-unread-clearing-and-catchup-mode.md create mode 100644 문서/67-announcement-broadcast-and-read-receipt-policy.md create mode 100644 문서/68-inline-quick-actions-and-command-chips.md create mode 100644 문서/69-search-zero-state-recents-and-saved-searches.md create mode 100644 문서/70-attachment-preview-collection-and-handoff.md create mode 100644 문서/71-meeting-briefing-before-during-and-after-flow.md create mode 100644 문서/72-decision-log-approvals-and-resolution-flow.md create mode 100644 문서/73-task-handoff-and-responsibility-markers.md create mode 100644 문서/74-shared-links-reference-panels.md create mode 100644 문서/75-sidebar-panels-pinned-slots-and-context-rail.md create mode 100644 문서/76-quick-phrases-templates-and-snippets.md create mode 100644 문서/77-notification-batching-digest-and-shift-modes.md create mode 100644 문서/78-offline-mode-sync-and-outbox-rules.md create mode 100644 문서/79-startup-resume-and-last-context-restoration.md create mode 100644 문서/80-desktop-layout-recipes.md create mode 100644 문서/81-mobile-one-thumb-navigation-patterns.md create mode 100644 문서/82-pwa-install-and-reentry.md create mode 100644 문서/83-profile-presence-and-status-card.md create mode 100644 문서/84-boundaries-block-mute-and-safety.md create mode 100644 문서/85-bookmarks-tags-and-collections.md create mode 100644 문서/86-empty-loading-and-error-trust-states.md create mode 100644 문서/87-reading-rhythm-density-and-scannability.md create mode 100644 문서/88-message-scheduling-delayed-send-and-unsend.md create mode 100644 문서/89-current-product-mobile-web-review-2026-04.md create mode 100644 문서/90-current-product-work-journey-review-2026-04.md create mode 100644 문서/91-current-product-friendly-journey-review-2026-04.md create mode 100644 문서/92-adoption-support-and-trust-kit.md create mode 100644 문서/93-team-rollout-and-first-week-checklists.md create mode 100644 문서/94-user-interview-script-and-study-guide.md create mode 100644 문서/95-beta-feedback-and-research-cadence.md create mode 100644 문서/96-experience-gap-implementation-backlog.md create mode 100644 문서/97-anti-patterns-and-what-not-to-build.md create mode 100644 문서/98-open-source-trust-surface-and-status-communication.md create mode 100644 문서/99-issue-triage-by-user-pain.md create mode 100644 문서/README.md create mode 100644 문서/atlas/01-work-inbox/01-triage-modes.md create mode 100644 문서/atlas/01-work-inbox/02-role-queues.md create mode 100644 문서/atlas/01-work-inbox/03-urgent-important.md create mode 100644 문서/atlas/01-work-inbox/04-day-start-panel.md create mode 100644 문서/atlas/01-work-inbox/05-catch-up-plan.md create mode 100644 문서/atlas/01-work-inbox/06-shift-handover.md create mode 100644 문서/atlas/01-work-inbox/07-unread-debt.md create mode 100644 문서/atlas/01-work-inbox/08-approval-queue.md create mode 100644 문서/atlas/01-work-inbox/09-stakeholder-ring.md create mode 100644 문서/atlas/01-work-inbox/10-decision-extract.md create mode 100644 문서/atlas/01-work-inbox/11-status-followup.md create mode 100644 문서/atlas/01-work-inbox/12-quiet-work.md create mode 100644 문서/atlas/01-work-inbox/13-work-bookmarks.md create mode 100644 문서/atlas/01-work-inbox/14-starred-as-tasks.md create mode 100644 문서/atlas/01-work-inbox/15-daily-brief.md create mode 100644 문서/atlas/01-work-inbox/16-weekly-digest.md create mode 100644 문서/atlas/01-work-inbox/17-archive-recall.md create mode 100644 문서/atlas/01-work-inbox/18-sla-hints.md create mode 100644 문서/atlas/01-work-inbox/19-ownership-chips.md create mode 100644 문서/atlas/01-work-inbox/20-escalation-ladder.md create mode 100644 문서/atlas/01-work-inbox/README.md create mode 100644 문서/atlas/02-search-knowledge/01-global-search-home.md create mode 100644 문서/atlas/02-search-knowledge/02-query-grammar.md create mode 100644 문서/atlas/02-search-knowledge/03-recents-and-history.md create mode 100644 문서/atlas/02-search-knowledge/04-work-rediscovery.md create mode 100644 문서/atlas/02-search-knowledge/05-friendly-rediscovery.md create mode 100644 문서/atlas/02-search-knowledge/06-people-rooms-files.md create mode 100644 문서/atlas/02-search-knowledge/07-zero-result-recovery.md create mode 100644 문서/atlas/02-search-knowledge/08-saved-searches.md create mode 100644 문서/atlas/02-search-knowledge/09-search-filters.md create mode 100644 문서/atlas/02-search-knowledge/10-ranking-explainability.md create mode 100644 문서/atlas/02-search-knowledge/11-link-and-file-recall.md create mode 100644 문서/atlas/02-search-knowledge/12-search-from-message.md create mode 100644 문서/atlas/02-search-knowledge/13-keyword-suggestions.md create mode 100644 문서/atlas/02-search-knowledge/14-time-range-pivots.md create mode 100644 문서/atlas/02-search-knowledge/15-search-shortcuts.md create mode 100644 문서/atlas/02-search-knowledge/16-search-quality-signals.md create mode 100644 문서/atlas/02-search-knowledge/17-knowledge-panels.md create mode 100644 문서/atlas/02-search-knowledge/18-search-empty-states.md create mode 100644 문서/atlas/02-search-knowledge/19-search-privacy.md create mode 100644 문서/atlas/02-search-knowledge/20-search-release-gates.md create mode 100644 문서/atlas/02-search-knowledge/README.md create mode 100644 문서/atlas/03-meetings-approvals/01-agenda-room.md create mode 100644 문서/atlas/03-meetings-approvals/02-brief-before.md create mode 100644 문서/atlas/03-meetings-approvals/03-live-decisions.md create mode 100644 문서/atlas/03-meetings-approvals/04-action-items.md create mode 100644 문서/atlas/03-meetings-approvals/05-approval-requests.md create mode 100644 문서/atlas/03-meetings-approvals/06-summary-after.md create mode 100644 문서/atlas/03-meetings-approvals/07-followup-reminders.md create mode 100644 문서/atlas/03-meetings-approvals/08-participant-state.md create mode 100644 문서/atlas/03-meetings-approvals/09-presenter-mode.md create mode 100644 문서/atlas/03-meetings-approvals/10-minute-selfchat.md create mode 100644 문서/atlas/03-meetings-approvals/11-quick-votes.md create mode 100644 문서/atlas/03-meetings-approvals/12-task-from-message.md create mode 100644 문서/atlas/03-meetings-approvals/13-due-date-flow.md create mode 100644 문서/atlas/03-meetings-approvals/14-meeting-pin.md create mode 100644 문서/atlas/03-meetings-approvals/15-doc-sharing.md create mode 100644 문서/atlas/03-meetings-approvals/16-search-presets.md create mode 100644 문서/atlas/03-meetings-approvals/17-handoff-after.md create mode 100644 문서/atlas/03-meetings-approvals/18-async-board.md create mode 100644 문서/atlas/03-meetings-approvals/19-approval-escalation.md create mode 100644 문서/atlas/03-meetings-approvals/20-fatigue-controls.md create mode 100644 문서/atlas/03-meetings-approvals/README.md create mode 100644 문서/atlas/04-friendly-conversation/01-light-entry.md create mode 100644 문서/atlas/04-friendly-conversation/02-low-pressure-reply.md create mode 100644 문서/atlas/04-friendly-conversation/03-reaction-rhythm.md create mode 100644 문서/atlas/04-friendly-conversation/04-sticker-gap-policy.md create mode 100644 문서/atlas/04-friendly-conversation/05-friend-group-noise.md create mode 100644 문서/atlas/04-friendly-conversation/06-private-boundaries.md create mode 100644 문서/atlas/04-friendly-conversation/07-birthdays-events.md create mode 100644 문서/atlas/04-friendly-conversation/08-photo-first-sharing.md create mode 100644 문서/atlas/04-friendly-conversation/09-casual-plans.md create mode 100644 문서/atlas/04-friendly-conversation/10-voice-note-future.md create mode 100644 문서/atlas/04-friendly-conversation/11-mood-status.md create mode 100644 문서/atlas/04-friendly-conversation/12-lightbookmarks.md create mode 100644 문서/atlas/04-friendly-conversation/13-context-memory.md create mode 100644 문서/atlas/04-friendly-conversation/14-warm-empty-states.md create mode 100644 문서/atlas/04-friendly-conversation/15-social-reentry.md create mode 100644 문서/atlas/04-friendly-conversation/16-soft-notifications.md create mode 100644 문서/atlas/04-friendly-conversation/17-close-friends-surface.md create mode 100644 문서/atlas/04-friendly-conversation/18-fun-without-noise.md create mode 100644 문서/atlas/04-friendly-conversation/19-privacy-in-friend-chat.md create mode 100644 문서/atlas/04-friendly-conversation/20-friendly-review-rubric.md create mode 100644 문서/atlas/04-friendly-conversation/README.md create mode 100644 문서/atlas/05-desktop-productivity/01-resizable-shell.md create mode 100644 문서/atlas/05-desktop-productivity/02-multiwindow-rules.md create mode 100644 문서/atlas/05-desktop-productivity/03-split-panels.md create mode 100644 문서/atlas/05-desktop-productivity/04-popout-chat.md create mode 100644 문서/atlas/05-desktop-productivity/05-keyboard-first.md create mode 100644 문서/atlas/05-desktop-productivity/06-command-surface.md create mode 100644 문서/atlas/05-desktop-productivity/07-search-overlay.md create mode 100644 문서/atlas/05-desktop-productivity/08-context-rail.md create mode 100644 문서/atlas/05-desktop-productivity/09-attachment-sidepanel.md create mode 100644 문서/atlas/05-desktop-productivity/10-notes-column.md create mode 100644 문서/atlas/05-desktop-productivity/11-compact-density.md create mode 100644 문서/atlas/05-desktop-productivity/12-window-memory.md create mode 100644 문서/atlas/05-desktop-productivity/13-monitor-aware-layout.md create mode 100644 문서/atlas/05-desktop-productivity/14-desktop-notifications.md create mode 100644 문서/atlas/05-desktop-productivity/15-clipboard-accelerators.md create mode 100644 문서/atlas/05-desktop-productivity/16-desktop-offline-cues.md create mode 100644 문서/atlas/05-desktop-productivity/17-window-reentry.md create mode 100644 문서/atlas/05-desktop-productivity/18-presentation-quiet.md create mode 100644 문서/atlas/05-desktop-productivity/19-admin-power-user.md create mode 100644 문서/atlas/05-desktop-productivity/20-desktop-release-checks.md create mode 100644 문서/atlas/05-desktop-productivity/README.md create mode 100644 문서/atlas/06-mobile-ergonomics/01-thumb-zones.md create mode 100644 문서/atlas/06-mobile-ergonomics/02-bottom-nav-purpose.md create mode 100644 문서/atlas/06-mobile-ergonomics/03-list-density.md create mode 100644 문서/atlas/06-mobile-ergonomics/04-input-ergonomics.md create mode 100644 문서/atlas/06-mobile-ergonomics/05-search-entry.md create mode 100644 문서/atlas/06-mobile-ergonomics/06-saved-patterns.md create mode 100644 문서/atlas/06-mobile-ergonomics/07-me-space-minimal.md create mode 100644 문서/atlas/06-mobile-ergonomics/08-refresh-vs-action.md create mode 100644 문서/atlas/06-mobile-ergonomics/09-safe-area.md create mode 100644 문서/atlas/06-mobile-ergonomics/10-scroll-preserve.md create mode 100644 문서/atlas/06-mobile-ergonomics/11-keyboard-open.md create mode 100644 문서/atlas/06-mobile-ergonomics/12-gesture-back.md create mode 100644 문서/atlas/06-mobile-ergonomics/13-context-restraint.md create mode 100644 문서/atlas/06-mobile-ergonomics/14-small-copy.md create mode 100644 문서/atlas/06-mobile-ergonomics/15-long-session.md create mode 100644 문서/atlas/06-mobile-ergonomics/16-status-chips.md create mode 100644 문서/atlas/06-mobile-ergonomics/17-attachment-future.md create mode 100644 문서/atlas/06-mobile-ergonomics/18-offline-cues.md create mode 100644 문서/atlas/06-mobile-ergonomics/19-install-cue.md create mode 100644 문서/atlas/06-mobile-ergonomics/20-battery-courtesy.md create mode 100644 문서/atlas/06-mobile-ergonomics/README.md create mode 100644 문서/atlas/07-trust-recovery/01-trust-entry-copy.md create mode 100644 문서/atlas/07-trust-recovery/02-session-expiry.md create mode 100644 문서/atlas/07-trust-recovery/03-last-good-state.md create mode 100644 문서/atlas/07-trust-recovery/04-reconnect-language.md create mode 100644 문서/atlas/07-trust-recovery/05-auth-error-ownership.md create mode 100644 문서/atlas/07-trust-recovery/06-device-list-trust.md create mode 100644 문서/atlas/07-trust-recovery/07-remote-signout.md create mode 100644 문서/atlas/07-trust-recovery/08-data-safety-phrasing.md create mode 100644 문서/atlas/07-trust-recovery/09-empty-vs-error.md create mode 100644 문서/atlas/07-trust-recovery/10-loading-honesty.md create mode 100644 문서/atlas/07-trust-recovery/11-outbox-recovery.md create mode 100644 문서/atlas/07-trust-recovery/12-message-failure.md create mode 100644 문서/atlas/07-trust-recovery/13-version-mismatch.md create mode 100644 문서/atlas/07-trust-recovery/14-maintenance-notice.md create mode 100644 문서/atlas/07-trust-recovery/15-download-integrity.md create mode 100644 문서/atlas/07-trust-recovery/16-public-status-surface.md create mode 100644 문서/atlas/07-trust-recovery/17-support-route.md create mode 100644 문서/atlas/07-trust-recovery/18-privacy-controls.md create mode 100644 문서/atlas/07-trust-recovery/19-recovery-metrics.md create mode 100644 문서/atlas/07-trust-recovery/20-trust-release-gate.md create mode 100644 문서/atlas/07-trust-recovery/README.md create mode 100644 문서/atlas/08-notifications-presence/01-notification-ladder.md create mode 100644 문서/atlas/08-notifications-presence/02-quiet-mode.md create mode 100644 문서/atlas/08-notifications-presence/03-shift-mode.md create mode 100644 문서/atlas/08-notifications-presence/04-digest-mode.md create mode 100644 문서/atlas/08-notifications-presence/05-mention-priority.md create mode 100644 문서/atlas/08-notifications-presence/06-presence-states.md create mode 100644 문서/atlas/08-notifications-presence/07-workload-status.md create mode 100644 문서/atlas/08-notifications-presence/08-away-back.md create mode 100644 문서/atlas/08-notifications-presence/09-summary-copy.md create mode 100644 문서/atlas/08-notifications-presence/10-per-thread-control.md create mode 100644 문서/atlas/08-notifications-presence/11-pinned-bundles.md create mode 100644 문서/atlas/08-notifications-presence/12-calendar-silence.md create mode 100644 문서/atlas/08-notifications-presence/13-friend-work-split.md create mode 100644 문서/atlas/08-notifications-presence/14-lockscreen-privacy.md create mode 100644 문서/atlas/08-notifications-presence/15-cross-device-noti.md create mode 100644 문서/atlas/08-notifications-presence/16-after-muted.md create mode 100644 문서/atlas/08-notifications-presence/17-fatigue-score.md create mode 100644 문서/atlas/08-notifications-presence/18-badge-semantics.md create mode 100644 문서/atlas/08-notifications-presence/19-urgency-levels.md create mode 100644 문서/atlas/08-notifications-presence/20-noti-analytics.md create mode 100644 문서/atlas/08-notifications-presence/README.md create mode 100644 문서/atlas/09-adoption-rollout/01-first-week-rollout.md create mode 100644 문서/atlas/09-adoption-rollout/02-role-onboarding.md create mode 100644 문서/atlas/09-adoption-rollout/03-invite-campaigns.md create mode 100644 문서/atlas/09-adoption-rollout/04-migration-checklist.md create mode 100644 문서/atlas/09-adoption-rollout/05-office-starter.md create mode 100644 문서/atlas/09-adoption-rollout/06-lead-starter.md create mode 100644 문서/atlas/09-adoption-rollout/07-support-macros.md create mode 100644 문서/atlas/09-adoption-rollout/08-adoption-metrics.md create mode 100644 문서/atlas/09-adoption-rollout/09-champion-program.md create mode 100644 문서/atlas/09-adoption-rollout/10-training-deck.md create mode 100644 문서/atlas/09-adoption-rollout/11-release-notes-users.md create mode 100644 문서/atlas/09-adoption-rollout/12-team-faq.md create mode 100644 문서/atlas/09-adoption-rollout/13-support-triage.md create mode 100644 문서/atlas/09-adoption-rollout/14-change-management.md create mode 100644 문서/atlas/09-adoption-rollout/15-reactivation.md create mode 100644 문서/atlas/09-adoption-rollout/16-onboarding-experiments.md create mode 100644 문서/atlas/09-adoption-rollout/17-trust-messages.md create mode 100644 문서/atlas/09-adoption-rollout/18-beta-feedback.md create mode 100644 문서/atlas/09-adoption-rollout/19-community-funnel.md create mode 100644 문서/atlas/09-adoption-rollout/20-persona-education.md create mode 100644 문서/atlas/09-adoption-rollout/README.md create mode 100644 문서/atlas/10-release-ops/01-version-manifest.md create mode 100644 문서/atlas/10-release-ops/02-windows-release-pipeline.md create mode 100644 문서/atlas/10-release-ops/03-web-release-pipeline.md create mode 100644 문서/atlas/10-release-ops/04-android-release-pipeline.md create mode 100644 문서/atlas/10-release-ops/05-download-host-routing.md create mode 100644 문서/atlas/10-release-ops/06-release-notes-style.md create mode 100644 문서/atlas/10-release-ops/07-screenshot-discipline.md create mode 100644 문서/atlas/10-release-ops/08-checksum-signature.md create mode 100644 문서/atlas/10-release-ops/09-rollback-playbook.md create mode 100644 문서/atlas/10-release-ops/10-observability-surface.md create mode 100644 문서/atlas/10-release-ops/11-backup-drills.md create mode 100644 문서/atlas/10-release-ops/12-alert-thresholds.md create mode 100644 문서/atlas/10-release-ops/13-secret-handling.md create mode 100644 문서/atlas/10-release-ops/14-public-status-page.md create mode 100644 문서/atlas/10-release-ops/15-release-truthfulness.md create mode 100644 문서/atlas/10-release-ops/16-artifact-retention.md create mode 100644 문서/atlas/10-release-ops/17-gitea-release-flow.md create mode 100644 문서/atlas/10-release-ops/18-os-route-design.md create mode 100644 문서/atlas/10-release-ops/19-release-regression.md create mode 100644 문서/atlas/10-release-ops/20-ops-readiness-gate.md create mode 100644 문서/atlas/10-release-ops/README.md create mode 100644 문서/atlas/11-qa-reviews/01-current-mobile-overall.md create mode 100644 문서/atlas/11-qa-reviews/02-mobile-first-three-minutes.md create mode 100644 문서/atlas/11-qa-reviews/03-mobile-navigation-clarity.md create mode 100644 문서/atlas/11-qa-reviews/04-mobile-first-room-emptiness.md create mode 100644 문서/atlas/11-qa-reviews/05-mobile-search-depth.md create mode 100644 문서/atlas/11-qa-reviews/06-mobile-saved-area-credibility.md create mode 100644 문서/atlas/11-qa-reviews/07-mobile-session-trust-copy.md create mode 100644 문서/atlas/11-qa-reviews/08-mobile-empty-state-surface.md create mode 100644 문서/atlas/11-qa-reviews/09-windows-alpha-first-impression.md create mode 100644 문서/atlas/11-qa-reviews/10-windows-resize-and-density.md create mode 100644 문서/atlas/11-qa-reviews/11-windows-multiwindow-priority.md create mode 100644 문서/atlas/11-qa-reviews/12-desktop-search-readiness.md create mode 100644 문서/atlas/11-qa-reviews/13-cross-platform-consistency.md create mode 100644 문서/atlas/11-qa-reviews/14-onboarding-failure-taxonomy.md create mode 100644 문서/atlas/11-qa-reviews/15-session-recovery-failure-matrix.md create mode 100644 문서/atlas/11-qa-reviews/16-search-zero-result-qa.md create mode 100644 문서/atlas/11-qa-reviews/17-critical-gaps-top10.md create mode 100644 문서/atlas/11-qa-reviews/18-skeptical-user-memo.md create mode 100644 문서/atlas/11-qa-reviews/19-release-surface-honesty.md create mode 100644 문서/atlas/11-qa-reviews/20-version-regression-tracker.md create mode 100644 문서/atlas/11-qa-reviews/README.md create mode 100644 문서/atlas/12-android-parallel/01-android-ia.md create mode 100644 문서/atlas/12-android-parallel/02-android-nav.md create mode 100644 문서/atlas/12-android-parallel/03-android-density.md create mode 100644 문서/atlas/12-android-parallel/04-android-composer.md create mode 100644 문서/atlas/12-android-parallel/05-android-notification-bridge.md create mode 100644 문서/atlas/12-android-parallel/06-android-work-profile.md create mode 100644 문서/atlas/12-android-parallel/07-android-storage.md create mode 100644 문서/atlas/12-android-parallel/08-android-apk-policy.md create mode 100644 문서/atlas/12-android-parallel/09-android-install-onboarding.md create mode 100644 문서/atlas/12-android-parallel/10-android-handoff.md create mode 100644 문서/atlas/12-android-parallel/11-android-material3.md create mode 100644 문서/atlas/12-android-parallel/12-android-tablet.md create mode 100644 문서/atlas/12-android-parallel/13-android-attachments.md create mode 100644 문서/atlas/12-android-parallel/14-android-permissions.md create mode 100644 문서/atlas/12-android-parallel/15-android-battery-data.md create mode 100644 문서/atlas/12-android-parallel/16-android-search.md create mode 100644 문서/atlas/12-android-parallel/17-android-saved-area.md create mode 100644 문서/atlas/12-android-parallel/18-android-offline.md create mode 100644 문서/atlas/12-android-parallel/19-android-trust-privacy.md create mode 100644 문서/atlas/12-android-parallel/20-android-parity-roadmap.md create mode 100644 문서/atlas/12-android-parallel/README.md create mode 100644 문서/atlas/README.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2c5da91 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.github +.workspace-secrets +artifacts +docs/assets +release-assets +src/VsMessenger.Web/node_modules +src/VsMessenger.Web/dist +**/bin +**/obj +**/*.tsbuildinfo diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..12412f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: 버그나 회귀를 기록합니다 +title: "[bug] " +labels: bug +assignees: '' +--- + +## 요약 + +문제를 한 문장으로 적어 주세요. + +## 재현 절차 + +1. +2. +3. + +## 기대 결과 + +원래 기대한 동작을 적어 주세요. + +## 실제 결과 + +실제 나타난 동작을 적어 주세요. + +## 관련 문서 / 스크린샷 + +상태 문서, README, 스크린샷, 릴리즈 링크가 있으면 같이 적어 주세요. + +## 환경 + +- OS: +- 앱 버전: +- 빌드 경로: + +## 추가 정보 + +스크린샷, 로그, 관련 문서를 적어 주세요. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ed7f715 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Live web + url: https://vstalk.phy.kr + about: 현재 공개 중인 모바일 웹 진입점을 직접 확인할 수 있습니다. + - name: Download mirror + url: https://download-vstalk.phy.kr + about: 공식 다운로드 미러 주소입니다. + - name: Release channels + url: https://github.com/werther24601/kotalk/releases + about: 공개 저장소 릴리즈 경로를 확인할 수 있습니다. + - name: Security contact + url: mailto:ian@physia.kr + about: 보안 이슈는 공개 이슈 대신 메일로 먼저 알려 주세요. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..951649b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,35 @@ +--- +name: Feature request +about: 기능 제안이나 개선안을 기록합니다 +title: "[feature] " +labels: enhancement +assignees: '' +--- + +## 배경 + +왜 필요한지 적어 주세요. + +## 제안 + +원하는 동작을 구체적으로 적어 주세요. + +## 기대 효과 + +누가 어떤 점에서 더 편해지는지 적어 주세요. + +## 영향 채널 + +- Windows +- Mobile Web +- Android +- Release / Deploy +- Docs + +## 현재 한계와의 관계 + +`PROJECT_STATUS.md`, `ROADMAP.md`, `문서/` 중 관련 항목이 있으면 적어 주세요. + +## 관련 문서 + +관련된 `문서/` 파일이 있으면 링크해 주세요. diff --git a/.github/ISSUE_TEMPLATE/ux_review.md b/.github/ISSUE_TEMPLATE/ux_review.md new file mode 100644 index 0000000..6ca867a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ux_review.md @@ -0,0 +1,34 @@ +--- +name: UX review +about: 사용 흐름, 정보구조, 문구, 피로도 관점의 리뷰를 남깁니다 +title: "[ux] " +labels: enhancement +assignees: '' +--- + +## 어떤 흐름인가요 + +- 온보딩 / 가입 +- 대화 목록 +- 검색 / 재탐색 +- 대화 / 답장 +- 세션 복구 +- 릴리즈 / 다운로드 + +## 현재 불편한 점 + +사용자 입장에서 어떤 지점이 막히거나 피로한지 적어 주세요. + +## 기대하는 방향 + +더 간편해지기 위해 어떤 변화가 필요하다고 보는지 적어 주세요. + +## 영향 채널 + +- Windows +- Mobile Web +- Android + +## 관련 근거 + +스크린샷, 영상, 문서 링크, 실제 사용 사례가 있으면 같이 남겨 주세요. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b0194fa --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## Summary + +- 무엇을 바꿨는지 +- 왜 바꿨는지 + +## Scope + +- 영향 채널: Windows / Mobile Web / Android / Release / Docs +- 관련 문서: +- UI 변경 시 스크린샷: + +## Checklist + +- [ ] README 또는 `문서/` 반영 여부 확인 +- [ ] CHANGELOG 반영 여부 확인 +- [ ] 가입/보안/릴리즈 정책 영향 확인 +- [ ] 다운로드 경로 영향 확인 (`download-vstalk.phy.kr`) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..82c541e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: ci + +on: + push: + branches: + - main + - feat/** + - release/** + pull_request: + +jobs: + server: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Restore + run: dotnet restore PhysOn.sln + + - name: Build API + run: dotnet build src/PhysOn.Api/PhysOn.Api.csproj -c Release --no-restore + + - name: Build Worker + run: dotnet build src/PhysOn.Worker/PhysOn.Worker.csproj -c Release --no-restore + + - name: Run API integration tests + run: dotnet test tests/PhysOn.Api.IntegrationTests/PhysOn.Api.IntegrationTests.csproj -c Release --no-restore + + desktop-windows: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Restore desktop project + run: dotnet restore src/PhysOn.Desktop/PhysOn.Desktop.csproj + + - name: Build desktop project + run: dotnet build src/PhysOn.Desktop/PhysOn.Desktop.csproj -c Release --no-restore diff --git a/.github/workflows/release-portable.yml b/.github/workflows/release-portable.yml new file mode 100644 index 0000000..a4c4bc6 --- /dev/null +++ b/.github/workflows/release-portable.yml @@ -0,0 +1,231 @@ +name: release-clients + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + version: + description: "릴리즈 버전. 예: v0.1.0-alpha.1" + required: true + type: string + channel: + description: "릴리즈 채널" + required: true + default: alpha + type: choice + options: + - alpha + - beta + - rc + - stable + upload_to_vps: + description: "준비된 릴리즈 번들을 VPS 다운로드 호스트로 업로드" + required: true + default: false + type: boolean + +permissions: + contents: read + +jobs: + build-windows: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Restore desktop project + run: dotnet restore src/VsMessenger.Desktop/VsMessenger.Desktop.csproj + + - name: Publish portable desktop build + run: dotnet publish src/VsMessenger.Desktop/VsMessenger.Desktop.csproj -c Release -r win-x64 --self-contained true -o out/win-x64 + + - name: Create portable ZIP + shell: pwsh + run: Compress-Archive -Path out/win-x64/* -DestinationPath out/VsMessenger-win-x64.zip + + - name: Upload Windows artifact + uses: actions/upload-artifact@v4 + with: + name: windows-portable + path: out/VsMessenger-win-x64.zip + + build-android: + if: ${{ hashFiles('src/VsMessenger.Mobile.Android/*.csproj') != '' }} + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Install Android workload + run: dotnet workload install android + + - name: Restore Android project + run: dotnet restore src/VsMessenger.Mobile.Android/VsMessenger.Mobile.Android.csproj + + - name: Publish Android APK + run: | + dotnet publish src/VsMessenger.Mobile.Android/VsMessenger.Mobile.Android.csproj \ + -c Release \ + -f net8.0-android \ + -p:AndroidPackageFormat=apk \ + -p:AndroidKeyStore=false \ + -o out/android + + - name: Collect Android artifact + run: | + apk_path="$(find out/android -type f -name '*.apk' | head -n 1)" + test -n "$apk_path" + cp "$apk_path" out/VsMessenger-android-universal.apk + + - name: Upload Android artifact + uses: actions/upload-artifact@v4 + with: + name: android-apk + path: out/VsMessenger-android-universal.apk + + assemble-release: + if: ${{ always() && needs.build-windows.result == 'success' && (needs.build-android.result == 'success' || needs.build-android.result == 'skipped') }} + needs: + - build-windows + - build-android + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download Windows artifact + uses: actions/download-artifact@v4 + with: + name: windows-portable + path: incoming/windows + + - name: Download Android artifact + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: android-apk + path: incoming/android + + - name: Prepare release bundle + env: + VERSION_INPUT: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} + CHANNEL_INPUT: ${{ github.event_name == 'workflow_dispatch' && inputs.channel || '' }} + run: | + chmod +x scripts/release/release-prepare-assets.sh + + channel="${CHANNEL_INPUT}" + if [[ -z "$channel" ]]; then + case "$VERSION_INPUT" in + *alpha*) channel="alpha" ;; + *beta*) channel="beta" ;; + *rc*) channel="rc" ;; + *) channel="stable" ;; + esac + fi + + prepare_args=( + --version "$VERSION_INPUT" + --channel "$channel" + --windows-zip incoming/windows/VsMessenger-win-x64.zip + --force + ) + + if [[ -f incoming/android/VsMessenger-android-universal.apk ]]; then + prepare_args+=(--android-apk incoming/android/VsMessenger-android-universal.apk) + fi + + ./scripts/release/release-prepare-assets.sh \ + "${prepare_args[@]}" + + - name: Upload release bundle + uses: actions/upload-artifact@v4 + with: + name: release-bundle + path: release-assets + + publish-forge-release: + if: ${{ secrets.FORGE_RELEASE_TOKEN != '' }} + needs: assemble-release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download release bundle + uses: actions/download-artifact@v4 + with: + name: release-bundle + path: . + + - name: Publish release to forge + env: + VERSION_INPUT: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }} + FORGE_RELEASE_TOKEN: ${{ secrets.FORGE_RELEASE_TOKEN }} + run: | + chmod +x scripts/release/release-publish-forge.sh + ./scripts/release/release-publish-forge.sh --version "$VERSION_INPUT" + + upload-to-vps: + if: github.event_name == 'workflow_dispatch' && inputs.upload_to_vps + needs: assemble-release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download release bundle + uses: actions/download-artifact@v4 + with: + name: release-bundle + path: . + + - name: Configure SSH key + env: + DOWNLOAD_SSH_KEY: ${{ secrets.DOWNLOAD_SSH_KEY }} + run: | + test -n "$DOWNLOAD_SSH_KEY" + install -d -m 700 ~/.ssh + printf '%s\n' "$DOWNLOAD_SSH_KEY" > ~/.ssh/download_host + chmod 600 ~/.ssh/download_host + + - name: Upload bundle to download host + env: + VERSION_INPUT: ${{ inputs.version }} + DOWNLOAD_SSH_HOST: ${{ secrets.DOWNLOAD_SSH_HOST }} + DOWNLOAD_SSH_USER: ${{ secrets.DOWNLOAD_SSH_USER }} + DOWNLOAD_ROOT: ${{ secrets.DOWNLOAD_ROOT }} + run: | + test -n "$DOWNLOAD_SSH_HOST" + test -n "$DOWNLOAD_SSH_USER" + test -n "$DOWNLOAD_ROOT" + chmod +x scripts/release/release-upload-assets.sh + ./scripts/release/release-upload-assets.sh \ + --version "$VERSION_INPUT" \ + --host "$DOWNLOAD_SSH_HOST" \ + --user "$DOWNLOAD_SSH_USER" \ + --target "$DOWNLOAD_ROOT" \ + --ssh-key ~/.ssh/download_host diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e25d3db --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.workspace-secrets/ +.workspace-policy/ +.vs/ +.playwright-cli/ +.vite/ +artifacts/ +releases/ +output/ +*.db +*.sqlite +*.sqlite3 +**/bin/ +**/obj/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..a92bc8b --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,77 @@ +# Architecture + +## 시스템 구성도 + +```text +Windows Desktop (Avalonia 12) + | + REST / WebSocket + | +ASP.NET Core 8 API + | +SQLite (current local alpha) + | +PostgreSQL / Redis / MinIO (target VPS stack) +``` + +## 핵심 컴포넌트 역할 + +| 컴포넌트 | 역할 | +|---|---| +| `VsMessenger.Desktop` | 한국어 Windows UX, 세션 보존, 대화 목록/대화창, 전송 흐름 | +| `VsMessenger.Api` | 인증, 부트스트랩, 대화/메시지 REST API, WebSocket 엔드포인트 | +| `VsMessenger.Application` | 유스케이스와 서비스 로직 | +| `VsMessenger.Domain` | 계정, 세션, 대화, 메시지 등 핵심 도메인 모델 | +| `VsMessenger.Infrastructure` | DB, 토큰, 시계, 실시간 연결 허브 등 인프라 구현 | +| `release-assets` | 릴리즈 메타데이터, 체크섬, 스크린샷 번들 | +| `deploy` | VPS용 Compose, Caddy, systemd, Dockerfile 초안 | + +## 데이터 흐름 + +### 가입 + +1. 데스크톱 앱이 이름 + 초대코드를 보냅니다. +2. API가 초대코드를 검증하고 계정/세션을 생성합니다. +3. 앱은 반환된 세션을 저장하고 부트스트랩 데이터를 요청합니다. + +### 메시지 전송 + +1. 사용자가 텍스트를 입력합니다. +2. 앱이 REST API로 메시지를 전송합니다. +3. API가 메시지를 저장하고 관련 사용자에게 WebSocket 이벤트를 보냅니다. +4. 앱은 읽기 상태와 목록을 갱신합니다. + +## 현재 구조와 목표 구조 + +| 구분 | 현재 실행 구조 | 목표 배포 구조 | +|---|---|---| +| 클라이언트 | Avalonia 12 desktop | Windows x64 portable / 향후 설치형 | +| API 저장소 | SQLite | PostgreSQL | +| 실시간 | API 내 WebSocket | API + Redis 기반 팬아웃 보조 | +| 파일 저장 | 미구현 | MinIO | +| 리버스 프록시 | 로컬 직접 포트 | Caddy | +| 운영 환경 | 로컬/WSL 중심 | Rocky Linux VPS | + +## 보안 경계 + +- 데스크톱 앱은 세션과 사용자 데이터를 OS 환경에 맞게 최소한으로 저장해야 합니다. +- API는 메시지 본문과 민감 정보를 로그에 남기지 않아야 합니다. +- 실사용 배포 전에는 `root + 비밀번호 SSH` 상태의 VPS를 그대로 사용하지 않습니다. +- 공개 다운로드 채널은 TLS와 체크섬 검증을 전제로 합니다. + +보안 상세 정책은 [SECURITY.md](SECURITY.md)와 [문서/05-security-privacy-and-risk.md](문서/05-security-privacy-and-risk.md)를 참고하세요. + +## 기술 선택 이유 + +- `Avalonia 12`: 현재 워크스페이스에서 빠르게 데스크톱 UI를 반복하고 Windows portable 산출물을 만들기 쉬움 +- `.NET 8`: API와 데스크톱 모두에서 일관된 개발/배포 흐름 확보 +- `ASP.NET Core 8`: REST + WebSocket 수직 슬라이스를 빠르게 구성 가능 +- `SQLite`: Alpha 단계에서 복잡도와 운영 부담을 낮춤 +- `PostgreSQL / Redis / MinIO`: VPS 운영 단계에서 필요한 데이터/캐시/파일 분리를 준비 + +## 관련 문서 + +- 제품 전략: [문서/01-product-strategy-and-mvp.md](문서/01-product-strategy-and-mvp.md) +- Windows 앱 구조: [문서/03-windows-client-architecture.md](문서/03-windows-client-architecture.md) +- 서버/VPS 구조: [문서/04-chat-server-vps-architecture.md](문서/04-chat-server-vps-architecture.md) +- API 계약: [문서/13-v0.1-api-and-events-contract.md](문서/13-v0.1-api-and-events-contract.md) diff --git a/BACKGROUND.md b/BACKGROUND.md new file mode 100644 index 0000000..43e78e6 --- /dev/null +++ b/BACKGROUND.md @@ -0,0 +1,76 @@ +# Background + +KoTalk의 배경은 단순합니다. 많은 사람이 여전히 쓰는 기준 메신저를 부정하기보다, 최근 한국어 사용자들이 실제로 드러낸 피로와 미충족 수요를 더 조용하고 더 예측 가능한 제품 설계로 풀어 보자는 것입니다. + +## Why This Project Exists + +최근 국내 메신저 여론에서 반복적으로 읽히는 신호는 꽤 선명했습니다. + +- 대화보다 피드, 광고, 추천 콘텐츠가 먼저 보일 때 생기는 피로 +- 운영정책과 제재 기준이 충분히 설명되지 않을 때 생기는 불신 +- 개인정보와 프라이버시 이슈가 누적될 때 생기는 경계심 +- 장애가 반복될 때 생기는 “언제 또 끊길지 모른다”는 피로감 + +KoTalk는 이 신호를 자극적인 비판의 소재로 소비하지 않습니다. 대신, 메신저라면 적어도 아래 네 가지는 다시 잘해야 한다고 봅니다. + +- 대화와 답장이 가장 먼저 보일 것 +- 개인적 대화와 업무적 대화 모두에서 복귀가 짧을 것 +- 상태와 정책이 설명 가능할 것 +- 장애나 세션 끊김이 생겨도 복구 흐름이 예측 가능할 것 + +## Signals Seen In Public Coverage + +최근 공개 기사와 공식 자료를 기준으로 보면, KoTalk의 배경은 대체로 아래 네 가지 축으로 정리됩니다. + +| Signal | What public coverage suggests | KoTalk response | +|---|---|---| +| 메신저 본질에 대한 피로 | 2025년 국내 기사들은 카카오톡 대규모 개편 이후 친구 탭 피드화, 숏폼, 광고 노출, 전체 구조 변화에 대한 이용자 불만이 빠르게 커졌다고 정리했습니다. 사용자는 기능 확대보다 `대화 흐름이 덜 방해받는가`를 더 엄격하게 봤습니다. | KoTalk는 첫 화면을 대화와 재진입 흐름 중심으로 유지합니다. 광고, 피드, 추천 콘텐츠는 핵심 표면에서 의도적으로 밀어냅니다. | +| 프라이버시와 보안 신뢰 요구 | 오픈채팅 관련 개인정보 유출과 과징금 이슈는 대형 메신저일수록 프라이버시를 옵션이 아니라 기본 설계로 다뤄야 한다는 기대를 더 높였습니다. | KoTalk는 최소 수집, 명확한 데이터 경계, 세션 통제, 설명 가능한 보안 문서를 제품 표면의 일부로 둡니다. | +| 운영정책 설명 책임 | 안전 조치 자체보다도, 어디까지 제한하는지와 어떤 기준으로 처리하는지를 투명하게 설명해야 신뢰를 얻는다는 신호가 반복됐습니다. | KoTalk는 제한, 차단, 신고, 복구 절차를 가능한 한 짧고 이해 가능한 문장으로 정리하는 방향을 택합니다. | +| 안정성과 복원력 기대 | 메시지 지연, 로그인 오류, 접속 장애 보도는 이제 메신저 품질에서 `잠깐의 장애`보다 `반복될 때 어떻게 복구되는가`가 더 중요하다는 점을 드러냈습니다. | KoTalk는 재연결, 읽기 커서, 전송 실패 표시, 복구 상태 안내를 핵심 기능으로 봅니다. | + +## What KoTalk Is Trying To Rebuild + +KoTalk가 다시 붙잡으려는 감각은 “메신저가 해야 할 기본기”에 가깝습니다. + +- Windows에서 읽기와 답장이 편한 구조 +- 모바일에서 길게 설명을 읽지 않아도 되는 진입 +- 검색, 보관, 다시 열기처럼 나중에 필요한 대화를 다시 꺼내기 쉬운 흐름 +- 장식보다 밀도와 구조가 먼저 보이는 한국어 UI + +즉, 낯선 제품 철학을 강요하기보다 익숙한 대화 문법을 현대적인 방식으로 다시 정돈하는 프로젝트입니다. + +## Tone And Position + +이 프로젝트는 특정 서비스를 공격하거나, “누군가를 대체하기 위해 무너져야 한다”는 식으로 접근하지 않습니다. 오히려 아래와 같은 태도에 가깝습니다. + +- 지금의 기준 제품이 여전히 중요한 이유를 인정하기 +- 이용자가 실제로 말한 불편을 흘려보내지 않기 +- 설명 가능한 UI와 운영 표면을 만들기 +- 공개 저장소에서 보여지는 것과 실제 구현 상태를 최대한 맞추기 + +## What That Means For The Product Surface + +그래서 이 저장소의 공개 표면도 같은 방향을 따릅니다. + +- README에는 화면, 상태, 배경, 릴리즈 경로를 함께 둡니다. +- 상태 문서에는 잘 되는 것과 아직 부족한 것을 같이 적습니다. +- 스크린샷은 제품의 방향을 보여 주는 근거로 남깁니다. +- 긴 기획과 UX 확장 문서는 별도 문서 묶음으로 보관합니다. + +## Sources + +- 아주경제, `카카오톡 개편에 42% '불만'…"대체 메신저 언급까지 확산"`: +- 더팩트, `카카오톡 '국민 메신저' 위상 휘청…개편 실패 책임론 급부상`: +- YTN, `카톡 검열 논란`: +- YTN, `카카오톡, 오전 한때 6분간 접속·메시지 전송 오류`: +- 연합뉴스, `"최소 6만5천명 정보 유출"…카카오에 과징금 151억 '역대 최대'`: +- 배경 정리 장문: [문서/14-project-background-and-market-context.md](문서/14-project-background-and-market-context.md) + +## Read More + +- 배경과 여론 맥락의 긴 문서: [문서/14-project-background-and-market-context.md](문서/14-project-background-and-market-context.md) +- 제품 전략: [문서/01-product-strategy-and-mvp.md](문서/01-product-strategy-and-mvp.md) +- 업무형 UX 방향: [문서/22-work-communication-ux-playbook.md](문서/22-work-communication-ux-playbook.md) +- 현재 상태: [PROJECT_STATUS.md](PROJECT_STATUS.md) +- 현재 화면 묶음: [SHOWCASE.md](SHOWCASE.md) diff --git a/BRANCHING_STRATEGY.md b/BRANCHING_STRATEGY.md new file mode 100644 index 0000000..19647cb --- /dev/null +++ b/BRANCHING_STRATEGY.md @@ -0,0 +1,123 @@ +# Branching Strategy + +## 목적 + +문서 중심 기획 저장소에서 시작하더라도, 이후 구현 단계까지 이어질 수 있는 브랜치 전략을 미리 고정합니다. + +## 기본 원칙 + +- `main`은 내부 워크스페이스 기준선입니다. +- 커밋은 의미 단위로 작게 유지합니다. +- 내부 기본 원격으로의 푸시는 계속 빠르게 유지합니다. +- 공개용 브랜치는 `main`과 분리된 별도 계보로 관리합니다. +- 릴리즈와 다운로드 서브도메인 갱신은 `release/*` 브랜치에서 마감합니다. + +## 브랜치 종류 + +### `main` + +- 내부 워크스페이스 최신 상태 +- 자동 반영과 빠른 반복 작업의 기준선 +- README, CHANGELOG, 문서 인덱스가 항상 최신이어야 합니다. + +### `workspace/main` + +- 현재 내부 워크스페이스 기준을 보존하는 백업 성격 브랜치 +- 공개용 이력 재구성 전후의 안전 기준점 + +### `public/main` + +- 공개 레포용 큐레이션 브랜치 +- 내부 히스토리를 그대로 노출하지 않고, 공개 가능한 이력만 최소 선형 체인으로 유지합니다. +- 공개 배포 직전의 파일/문구/링크/히스토리 검수를 통과한 상태만 반영합니다. +- `main`에서 구조/네이밍 정리가 크게 들어가면 다시 재생성하거나 재큐레이션하는 것을 기본 원칙으로 둡니다. + +### `feat/` + +- 기능 개발용 브랜치 +- 예: `feat/auth-alpha`, `feat/winui-shell`, `feat/search-index` + +### `docs/` + +- 문서 보강/개편 전용 브랜치 +- 예: `docs/readme-overhaul`, `docs/release-playbook` + +### `release/` + +- 릴리즈 마감용 브랜치 +- 예: `release/v0.1.0-alpha.1` +- 아래 항목을 함께 정리합니다. + - 빌드 산출물 + - `CHANGELOG.md` + - README 상태 반영 + - 다운로드 경로 점검 + +### `hotfix/` + +- 배포 후 긴급 수정 +- 예: `hotfix/download-link`, `hotfix/auth-token-rotation` + +### `spike/` + +- 실험/검증 +- 장기 유지 코드로 합칠지 미정인 경우만 사용 + +## 머지 원칙 + +- 개인 저장소 기준으로도 PR 스타일 설명을 남깁니다. +- `main` 머지 전 확인 항목: + - README가 최신 상태인가 + - `문서/`의 결정과 충돌하지 않는가 + - CHANGELOG 반영이 필요한가 + - 다운로드/릴리즈 경로 영향이 있는가 +- `public/main` 반영 전 추가 확인 항목: + - 공개 부적합 링크나 내부 경로가 남아 있지 않은가 + - AI/프롬프트/에이전트 같은 메타 흔적이 남아 있지 않은가 + - 공개용 커밋 이력이 과도하거나 어색하지 않은가 + - 공개 릴리즈 문구가 현재 구현 상태를 넘어서지 않는가 + - `.workspace-*` 경로의 비공개 정책/시크릿이 추적 대상에 섞이지 않았는가 + +## 커밋 규칙 + +- 권장 접두사: + - `docs:` + - `feat:` + - `fix:` + - `release:` + - `chore:` +- 예: + - `docs: define korean-first signup policy` + - `feat: scaffold winui shell` + - `release: prepare alpha download endpoint` + +## 릴리즈 운영 규칙 + +릴리즈 브랜치에서는 아래를 반드시 같이 처리합니다. + +1. Windows 빌드 생성 +2. VPS 업로드 +3. `https://download-vstalk.phy.kr` 동작 확인 +4. README 버전/상태 갱신 +5. CHANGELOG 갱신 +6. 태그 생성 +7. `main` 반영 + +공개 릴리즈는 아래 순서를 따릅니다. + +1. 내부 기본 원격 반영 +2. 명시적 요청이 있을 때만 제2 공개 레포 반영 +3. 다시 명시적 요청이 있을 때만 제3 공개 레포 반영 + +제3 공개 레포는 항상 제2 공개 레포보다 늦게 공개하며, 제2 공개 레포 경로를 함께 명시합니다. + +## 문서 갱신 규칙 + +다음 변화가 있으면 README와 문서를 같이 갱신합니다. + +- 제품 방향 변경 +- 가입 방식 변경 +- 릴리즈 방식 변경 +- 다운로드 경로 변경 +- 우위/패리티 판단 변경 + +즉, 구현만 바꾸고 문서를 방치하지 않습니다. diff --git a/BUSINESS_MODEL.md b/BUSINESS_MODEL.md new file mode 100644 index 0000000..27778cb --- /dev/null +++ b/BUSINESS_MODEL.md @@ -0,0 +1,37 @@ +# Business Model + +KoTalk는 오픈소스 코어를 유지하면서, 운영 서비스와 지원을 별도 가치로 만드는 방식을 지향합니다. + +## Core Position + +- 저장소 코드는 읽고, 수정하고, 자체 배포할 수 있어야 합니다. +- 공식 운영 서비스는 호스팅, 운영 책임, 배포 지원, 조직 기능, 보안·감사성 같은 영역에서 가치를 만들어야 합니다. +- 공개 저장소와 운영 서비스는 같은 문장으로 뭉개지지 않게 설명합니다. + +## Why This Structure + +메신저는 기능만으로 끝나지 않습니다. 실제 도입에서는 운영 책임, 업데이트 경로, 지원, 보안 설명 가능성도 중요합니다. +KoTalk는 이 차이를 숨기지 않고, 코어와 서비스 경계를 분리해 설명하는 방식을 택합니다. + +## Three Surfaces + +| Surface | Meaning | +|---|---| +| Open-source core | 코드, 문서, 기본 배포 골격, 자체 호스팅 가능한 경로 | +| Official service | PHYSIA가 운영하는 서비스와 다운로드 미러 | +| Support and deployment packages | 운영 지원, 규제 환경 대응, 설치·운영 지원 | + +## What The Official Service Should Add + +- 관리형 배포와 업데이트 운영 +- 운영 책임과 장애 대응 +- 조직용 정책과 감사성 +- 도입 및 전환 지원 + +## What KoTalk Should Avoid + +- 코어 기능을 의도적으로 훼손해 유료 전환을 강제하는 구조 +- 구현보다 앞서는 과장된 문구 +- 검증 전 항목을 “즉시 도입 가능”처럼 말하는 방식 + +라이선스와 상표 경계는 [LICENSE-FAQ.md](LICENSE-FAQ.md), [TRADEMARKS.md](TRADEMARKS.md)를 참고하세요. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3d20205 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,93 @@ +# Changelog + +이 프로젝트는 현재 초기 설계 단계를 넘어 첫 실행 가능한 Alpha 프로토타입 단계에 들어갔습니다. + +모든 의미 있는 변경은 이 파일에 기록합니다. + +## [Unreleased] + +### Added + +- `KoTalk` 공개 브랜드 기준과 다운로드/릴리즈 표면 정리 +- 공개 가입 전략을 `1회성 인증 중심`으로 재정의한 기획 문서 보강 +- Apache-2.0 기준의 라이선스/상표/기여 정책 정리 +- 공개 루트 문서 전면 개편과 다운로드 경로 하이퍼링크 정리 + +- 한국어 Windows 메신저 프로젝트 방향 수립 +- `문서/` 기준의 마스터 기획 세트 작성 +- 최근 국내 카카오톡 여론을 반영한 프로젝트 배경/시장 맥락 문서 추가 +- `MIT License`, `CODE_OF_CONDUCT.md`, `DEVELOPMENT.md`, `ARCHITECTURE.md`, `ROADMAP.md`, `SUPPORT.md` 추가 +- Android 병렬 채널 전략 문서 추가 +- Forge Releases 게시 스크립트 추가 +- 한국어 UI 문체 시스템 문서 추가 +- 가입/온보딩/인증 정책 문서 추가 +- 카카오톡 PC 패리티/상위호환 매트릭스 문서 추가 +- 공개 저장소용 `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, `BRANCHING_STRATEGY.md` 추가 +- 릴리즈 다운로드 도메인 정책 반영: `https://download-vstalk.phy.kr` +- `ASP.NET Core + JWT + EF Core + SQLite + WebSocket` 기반 Alpha 서버 수직 슬라이스 +- `Avalonia 12 + .NET 8` 기반 Windows 데스크톱 Alpha 셸 +- `v0.1.0-alpha.1` Windows x64 portable zip 산출물 +- 릴리즈 번들 메타데이터, 체크섬, 스크린샷 생성 규약 +- VPS용 MVP 배포 스캐폴딩, Caddy 예시, 릴리즈 업로드 스크립트 +- `https://vstalk.phy.kr` 모바일 웹앱 실배포와 same-origin API 운영 경로 +- `PROJECT_STATUS.md`, `GOVERNANCE.md`, 저장소 전용 README 시각 자산 추가 +- `문서/18-white-material-compact-ui-system.md`, `문서/19-desktop-adaptive-window-and-multiwindow-guidelines.md`, `문서/20-kakao-public-pattern-benchmark-and-vs-translation.md` 추가 +- 사용자 여정별 점검 기준과 QA 문서 주제 강화를 위한 `문서/63-user-journey-review-framework-and-qa-topics.md` 추가 +- `COMMUNITY.md`, `MAINTAINERS.md`, `RELEASING.md`, `FIRST_CONTRIBUTION.md` 추가 +- README 전용 공개 저장소 시각 자산 `open-source-surface.svg`, `contribution-path.svg` 추가 +- UX 중심 저장소 표면을 위한 `ux_review` 이슈 템플릿 추가 +- 사용자 관점 리뷰와 비판적 QA 범주 확장을 위한 `문서/112-review-surface-expansion-and-critical-qa-proposal.md` 추가 +- 루트 120개 문서와 세부 아틀라스 253개 문서로 구성된 `문서/atlas/` 확장 세트 추가 +- 공개 저장소 첫 진입을 위한 `FAQ.md`, `SHOWCASE.md` 추가 +- README 전용 공개 표면 자산 `public-contract.svg`, `evaluation-paths.svg` 추가 +- 공개 사업모델 기준 문서 `BUSINESS_MODEL.md`, `문서/113-open-core-platform-business-and-procurement-strategy.md` 추가 +- 핵심 차별점 고정 문서 `문서/114-core-differentiation-pillars.md` 추가 +- `TRUST_CENTER.md`, `SECURITY_RESPONSE.md`, `DEPLOYMENT_MODES.md`, `PRIVACY_AND_DATA_HANDLING.md`, `PROCUREMENT_READINESS.md`, `PORTFOLIO_CAPABILITIES.md`, `TRADEMARKS.md`, `CONTRIBUTOR_LICENSE_POLICY.md`, `LICENSE-FAQ.md` 추가 + +### Changed + +- 공개 브랜드를 `KoTalk`로 정리하고 공개 문서의 직접적·내부지향 표현을 제거 +- README를 대중용 첫인상 기준으로 다시 구성하고 다운로드는 공식 미러와 저장소 릴리즈를 함께 표기 +- 보안/신뢰 문서에서 운영 힌트와 공유 접근값 노출을 줄이고 공개 범위를 재정의 +- 라이선스를 Apache-2.0으로 정리하고 일반 기여의 기본 규칙을 단순화 +- 공개 가입 정책을 `초대코드 중심`에서 `이메일/휴대폰 기반 1회성 인증` 방향으로 수정 +- 디자인 지침을 `각진, 플랫, 텍스트 최소화, 머터리얼 계열` 원칙으로 보강 + +- 제품 방향을 `복제형`이 아니라 `한국어 Windows 메신저 최적화`로 명확히 조정 +- 가입 정책을 `Alpha 즉시 실행형`과 `Beta 기본형`으로 분리 +- README와 마스터 문서 세트 전면 보강 +- 최근 기사와 공개 자료를 바탕으로 프로젝트 배경 설명을 신사적 톤으로 재구성 +- README를 스크린샷, 빠른 시작, 아키텍처, 로드맵 중심의 공개 저장소형 구조로 개편 +- 저장소 문서 링크를 원격에서 읽기 좋은 상대경로 기준으로 정리 +- 멀티플랫폼 릴리즈 구조, OS별 latest 라우트, 원격 Releases 연계 구조로 확장 +- 최신 기준 스크린샷을 원격 저장소에 함께 유지하는 정책 반영 +- 클라이언트 실제 구현 스택을 `WinUI 3 계획안`에서 `Avalonia 12 실행안`으로 조정 +- 프록시 환경에서 WebSocket URL이 `wss://`로 내려오도록 API forwarded headers 처리 추가 +- 배포 문서를 실제 운영 구조 기준 `Caddy + ASP.NET Core API + nginx webapp + SQLite`로 정정 +- README를 저장소 공개면 중심 구조로 전면 재구성 +- 데스크톱 UI를 모던 화이트/플랫/컴팩트 기준으로 대규모 개편하고 기본 서버 주소를 `https://vstalk.phy.kr`로 조정 +- 모바일 웹 UI를 화이트 원톤 메신저 셸로 재설계하고 `전체/안읽음/고정` 필터, 검색, 가입 직후 첫 대화 진입 흐름 추가 +- 최신 기준 README 스크린샷 자산을 현재 UI 목업과 모바일 웹 캡처 기준으로 갱신 +- 모바일 웹 세션 복구를 refresh token 회전 경쟁에 안전한 구조로 보강하고, 일반 네트워크 오류 시 마지막 정상 화면을 유지하도록 조정 +- 모바일 웹 대화 전환 시 초안이 다른 방으로 잠깐 보이는 상태 불일치를 줄이고, 자동 스크롤을 하단 근처 또는 내 전송 직후로 제한 +- 모바일 웹 상태 메시지를 온보딩/세션 화면에 맞게 분리하고, JSON이 아닌 오류 응답도 친화적 메시지로 처리 +- 모바일 웹 최신 기준 목록/대화 스크린샷 자동 캡처 스크립트 추가 +- 모바일 웹 하단 바를 목적지형 `대화/검색/보관/내 공간` 구조로 재편하고, 검색/보관/내 공간을 분리된 표면으로 1차 구현 +- 모바일 웹 온보딩 카피, 빈 상태 CTA, 내 공간 액션 배치를 덜 기술적이고 더 사용자 중심으로 정리 +- 모바일 웹 최신 기준 스크린샷에 검색 화면을 추가하고, 스크린샷 생성 스크립트 의존성을 재현 가능하게 정리 +- README, PROJECT_STATUS, 문서 인덱스에 강화된 사용자 리뷰 프레임 링크와 문서 규모를 반영 +- 업무/일상 UX 확장 문서를 `118개` 규모의 마스터 세트로 재구성하고, 모바일 웹 실사용 리뷰·저피로 UI 규칙·정보구조·adoption/support 문서를 추가 +- README 상단을 `신뢰/상태/진입 경로` 중심으로 재구성하고, 커뮤니티·메인테이너·릴리즈 문서로 공개면을 확장 +- Issue / PR 템플릿에 플랫폼, 문서 정합성, UX 리뷰 흐름을 더 명시적으로 반영 +- 사용자 관점 리뷰 체계를 `119개` 문서 규모로 확장하고, 다음 단계 플랫폼별/실패유형별 QA 분리 제안서를 추가 +- 문서 체계를 루트 120개 + 아틀라스 253개, 총 373개 문서 규모로 확장하고, 실제 모바일 웹 비판 리뷰와 세부 QA 아틀라스를 연결 +- 모바일 웹 버전을 `web-0.1.0-alpha.2`로 올리고, 첫 대화방 empty state를 행동 패널로 재설계 +- 모바일 웹 검색을 대화/최근 메시지 기반 재발견 표면으로 확장하고 결과를 메시지/대화 섹션으로 분리 +- 모바일 웹 보관함을 `답장 필요 / 중요 대화 / 최근 다시 열기` 허브로 재구성 +- 세션 신뢰 카피를 현재 화면 유지 중심으로 조정하고, reconnect 후 최신 메시지 재동기화와 초기 WebSocket 재연결을 보강 +- 최신 모바일 웹 스크린샷 세트에 `보관함` 화면을 추가하고 캡처 스크립트를 확장 +- README 상단을 즉시 체험형 CTA, 공개 계약, 평가 경로, FAQ/Showcase 중심으로 재구성하고 이슈 진입 링크도 함께 정리 +- 저장소 전략 기준을 `오픈소스 코어 + 공식 플랫폼/관리형 운영 + 공공/기관 대응 가능성`으로 명문화하고 공개 문서에 반영 +- 범용성, 업무형 간편성, 멀티플랫폼, 셀프호스팅/내부망, 보안/운영 투명성, 커뮤니티 기반 개선 구조를 핵심 차별점으로 공개면과 전략 문서에 고정 +- 공개 저장소 표면에 메인테이너 실명, 활동명, GitHub 계정, 운영사 `PHYSIA`, 문의 채널을 일관된 기준으로 반영 +- 기본 JWT 서명키 거부, 세션 재검증, WebSocket 전용 티켓, 인증 no-store, 기본 rate limiting, 보수적 초대코드 시드 정책으로 기본 보안선을 강화 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e4a8f4a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ +# Code Of Conduct + +## 우리의 커뮤니티 원칙 + +이 프로젝트는 한국어 Windows 메신저를 함께 더 낫게 만드는 공개 협업 공간입니다. 참여자는 아래 원칙을 지켜야 합니다. + +- 기술적 의견 차이는 환영하지만, 사람을 공격하지 않습니다. +- 근거 없는 비난보다 재현 가능한 사실과 구체적 제안을 우선합니다. +- 초보 기여자도 이해할 수 있는 설명을 지향합니다. +- 사적인 조롱, 혐오 표현, 모욕, 집단 괴롭힘을 허용하지 않습니다. + +## 권장되는 참여 방식 + +- 재현 절차가 있는 버그 제보 +- 문서 개선, 오탈자 수정, 구조 정리 +- 제품 방향과 구현의 차이를 줄이는 제안 +- 테스트, 릴리즈, 배포 품질 개선 + +## 허용되지 않는 행위 + +- 인신공격, 비하, 혐오 표현 +- 반복적 도발, 괴롭힘, 위협 +- 타인의 개인정보 또는 비공개 정보 공개 +- 악성 스팸, 광고, 무관한 홍보 +- 보안 이슈를 사전 연락 없이 공개 이슈로 노출하는 행위 + +## 적용 범위 + +이 규범은 저장소 이슈, PR, 커밋 메시지, 코드 리뷰, 문서 토론, 릴리즈 메모 등 프로젝트와 관련된 공개 협업 전반에 적용됩니다. + +## 위반 시 조치 + +관리자는 상황에 따라 아래 조치를 할 수 있습니다. + +- 내용 수정 요청 +- 경고 +- 코멘트/이슈 잠금 +- 일시적 참여 제한 +- 영구 차단 + +## 신고 절차 + +행동 규범 위반이나 괴롭힘이 의심되면 아래로 알려 주세요. + +- `ian@physia.kr` + +가능하면 다음 정보를 함께 보내 주세요. + +- 발생 위치 링크 +- 관련 사용자 +- 상황 설명 +- 필요 시 스크린샷 또는 로그 diff --git a/COMMUNITY.md b/COMMUNITY.md new file mode 100644 index 0000000..6415196 --- /dev/null +++ b/COMMUNITY.md @@ -0,0 +1,30 @@ +# Community + +KoTalk는 아직 작은 알파 프로젝트이지만, 공개 저장소는 누구나 읽기 쉬운 상태를 유지하려고 합니다. + +## Where To Start + +- 제품을 먼저 보고 싶다면: [README.md](README.md), [SHOWCASE.md](SHOWCASE.md), [PROJECT_STATUS.md](PROJECT_STATUS.md) +- 기여를 시작하고 싶다면: [FIRST_CONTRIBUTION.md](FIRST_CONTRIBUTION.md), [CONTRIBUTING.md](CONTRIBUTING.md) +- 보안 이슈라면: [SECURITY.md](SECURITY.md) + +## Best First Contributions + +- 문서와 실제 상태 정합성 개선 +- 모바일 웹과 데스크톱 사용성 보강 +- 릴리즈, 스크린샷, 다운로드 경로 정리 +- 재현 가능한 버그 리포트 작성 + +## Good Community Signals + +- 문제보다 먼저 사용자 맥락을 설명합니다. +- 어떤 흐름이 왜 느린지, 무엇이 더 짧아져야 하는지 적습니다. +- 스크린샷, 운영체제, 버전, 재현 절차를 남깁니다. +- 문서와 구현이 엇갈리면 그 사실을 함께 적습니다. + +## Contact + +- 일반 도움: [help@physia.kr](mailto:help@physia.kr) +- 제휴/운영 문의: [contact@physia.kr](mailto:contact@physia.kr) + +운영 원칙은 [GOVERNANCE.md](GOVERNANCE.md), 메인테이너 정보는 [MAINTAINERS.md](MAINTAINERS.md)에서 확인할 수 있습니다. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7fcc19a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +KoTalk는 코드, 문서, 스크린샷, 릴리즈 표면을 함께 관리하는 저장소입니다. +좋은 기여는 기능 추가뿐 아니라 저장소 공개면이 더 정확하고 읽기 쉬워지는 방향까지 포함합니다. + +## Before You Start + +1. [README.md](README.md) +2. [PROJECT_STATUS.md](PROJECT_STATUS.md) +3. [ROADMAP.md](ROADMAP.md) +4. [DEVELOPMENT.md](DEVELOPMENT.md) +5. [GOVERNANCE.md](GOVERNANCE.md) + +## Contribution Principles + +- 한국어 UI, 낮은 피로도, 높은 밀도를 기본 방향으로 유지합니다. +- 큰 기능은 가능하면 문서로 먼저 방향을 맞춘 뒤 구현합니다. +- README와 상태 문서는 실제 구현보다 앞서 나가지 않습니다. +- 다운로드 경로나 릴리즈에 영향이 있으면 관련 문서를 함께 갱신합니다. + +## Good Contributions + +- 검색, 복귀, 보관, 멀티 윈도우 같은 핵심 메시징 흐름 개선 +- 문서와 실제 상태 정합성 보강 +- 릴리즈, 체크섬, 스크린샷 운영 개선 +- 재현 가능한 버그 리포트와 테스트 보강 + +## Pull Request Checklist + +- 무엇이 바뀌었는지 설명했는가 +- 왜 필요한지 설명했는가 +- 사용자 흐름, 릴리즈, 보안에 어떤 영향이 있는지 적었는가 +- 관련 문서 업데이트 필요 여부를 확인했는가 + +## Licensing + +일반적인 코드와 문서 기여는 이 저장소의 [Apache License 2.0](LICENSE) 조건으로 받습니다. +브랜드 자산, 로고, 별도 권리 정리가 필요한 자료는 [CONTRIBUTOR_LICENSE_POLICY.md](CONTRIBUTOR_LICENSE_POLICY.md)를 참고하세요. diff --git a/CONTRIBUTOR_LICENSE_POLICY.md b/CONTRIBUTOR_LICENSE_POLICY.md new file mode 100644 index 0000000..5c388fc --- /dev/null +++ b/CONTRIBUTOR_LICENSE_POLICY.md @@ -0,0 +1,18 @@ +# Contributor License Policy + +일반적인 코드와 문서 기여는 이 저장소의 [Apache License 2.0](LICENSE) 조건으로 받습니다. + +## Default Rule + +- PR을 제출하면 해당 변경을 이 저장소 라이선스 조건으로 제공할 권리가 있다고 간주합니다. +- 별도 CLA는 일반적인 코드와 문서 기여에 요구하지 않습니다. + +## Extra Review Cases + +아래 항목은 추가 확인이 필요할 수 있습니다. + +- 로고, 브랜드 자산, 디자인 원본 +- 제3자 권리가 얽힌 자료 +- 별도 재배포 제한이 있는 외부 자산 + +문의: [contact@physia.kr](mailto:contact@physia.kr) diff --git a/DEPLOYMENT_MODES.md b/DEPLOYMENT_MODES.md new file mode 100644 index 0000000..ad4144e --- /dev/null +++ b/DEPLOYMENT_MODES.md @@ -0,0 +1,18 @@ +# Deployment Modes + +KoTalk는 하나의 운영 방식만을 전제로 설명하지 않습니다. + +## Modes + +| Mode | Description | Current read | +|---|---|---| +| Official service | PHYSIA가 운영하는 공개 서비스와 다운로드 미러 | Alpha 운영 | +| Self-hosted | 저장소와 배포 골격을 직접 설치·운영 | 공개 문서 제공 | +| Private network | 외부 인터넷 없이 조직 내부에 설치 | 방향 정의 단계 | +| Dedicated environment | 조직별 분리 운영, 별도 지원 | 방향 정의 단계 | + +## Boundary Rules + +- 저장소 라이선스와 운영 서비스 약관은 같은 표면이 아닙니다. +- 공식 서비스의 운영 책임은 PHYSIA가 지고, 자체 호스팅 운영 책임은 설치 주체가 집니다. +- 폐쇄망과 규제 환경 배포는 “가능성”과 “검증 완료”를 구분해서 설명합니다. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..581d5f9 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,138 @@ +# Development Guide + +## Naming Note + +공개 브랜드는 `KoTalk`이지만, 현재 저장소의 프로젝트 파일과 네임스페이스는 아직 `PhysOn.*`를 사용합니다. +문서 개편이 먼저 진행 중이며, 코드 네임스페이스 정렬은 별도 작업으로 다룹니다. + +## Requirements + +- `.NET 8 SDK` +- Git +- Node.js 20+ +- Windows portable 빌드를 만들려면 `win-x64` publish 가능한 .NET 환경 + +Android 채널을 다룰 때는 아래가 추가로 필요합니다. + +- `OpenJDK 17+` +- `.NET Android workload` +- Android SDK / cmdline-tools + +## Quick Start + +```bash +git clone +cd vs-messanger +dotnet build PhysOn.sln -c Debug +``` + +## Run The API + +```bash +dotnet run --project src/PhysOn.Api --urls http://127.0.0.1:5082 +``` + +기본 확인 URL: + +- [http://127.0.0.1:5082/health](http://127.0.0.1:5082/health) +- [http://127.0.0.1:5082/](http://127.0.0.1:5082/) + +접근 게이트나 시드 값은 공개 문서에 고정하지 않습니다. 필요한 값은 로컬 환경 변수나 비공개 배포 설정에서 넣어야 합니다. + +## Run The Desktop Client + +```bash +dotnet run --project src/PhysOn.Desktop +``` + +기본 입력값: + +- 서버 주소: `http://127.0.0.1:5082` + +## Run The Mobile Web Client + +```bash +cd src/PhysOn.Web +npm install +npm run dev +``` + +기본 개발 주소: + +- 웹앱: [http://127.0.0.1:4173](http://127.0.0.1:4173) +- API 프록시 기본값: [http://127.0.0.1:5082](http://127.0.0.1:5082) + +## Test + +```bash +dotnet test tests/PhysOn.Api.IntegrationTests/PhysOn.Api.IntegrationTests.csproj +``` + +필요 시 전체 확인: + +```bash +dotnet build PhysOn.sln -c Debug +dotnet test PhysOn.sln -c Debug +``` + +## Release Builds + +Windows: + +```bash +dotnet publish src/PhysOn.Desktop/PhysOn.Desktop.csproj \ + -c Release \ + -r win-x64 \ + --self-contained true \ + -o artifacts/release/v0.1.0-alpha.1-win-x64 +``` + +Android: + +```bash +dotnet workload install android +dotnet publish src/PhysOn.Mobile.Android/PhysOn.Mobile.Android.csproj \ + -c Release \ + -f net8.0-android \ + -p:AndroidPackageFormat=apk \ + -o artifacts/release/android +``` + +공개 산출물 네이밍은 `KoTalk-*` 기준으로 정리하는 방향이고, 현재 내부 스크립트와 프로젝트명은 별도 정렬 단계에 있습니다. + +## Release Metadata + +```bash +./scripts/release/release-prepare-assets.sh \ + --version v0.1.0-alpha.1 \ + --channel alpha \ + --windows-zip artifacts/release/PhysOn-win-x64-v0.1.0-alpha.1.zip \ + --android-apk artifacts/release/PhysOn-android-universal-v0.1.0-alpha.1.apk \ + --screenshots artifacts/screenshots \ + --force +``` + +## Deployment Notes + +- 공개 웹 진입점: [vstalk.phy.kr](https://vstalk.phy.kr) +- 공식 다운로드 미러: [download-vstalk.phy.kr](https://download-vstalk.phy.kr) +- 저장소 릴리즈 경로: [RELEASING.md](RELEASING.md) + +실제 호스트 주소, 관리자 계정, 배포용 비밀값은 공개 문서에 적지 않습니다. + +## Troubleshooting + +### Desktop window does not open on Linux/WSL + +- X 서버 또는 데스크톱 세션이 있는지 확인합니다. +- GUI가 없는 환경이면 API와 테스트만 먼저 확인합니다. + +### Download links do not open + +- [download-vstalk.phy.kr](https://download-vstalk.phy.kr) DNS와 HTTPS 상태를 확인합니다. +- 저장소 릴리즈 경로가 최신인지 함께 확인합니다. + +### Web app does not open + +- [vstalk.phy.kr](https://vstalk.phy.kr) DNS와 프록시 상태를 확인합니다. +- 정적 파일 배포 루트가 맞는지 확인합니다. diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..7eb933f --- /dev/null +++ b/FAQ.md @@ -0,0 +1,60 @@ +# FAQ + +KoTalk를 처음 보는 사람을 위한 짧은 안내입니다. + +## KoTalk는 무엇인가요 + +KoTalk는 한국어 중심의 메시징 경험을 다시 설계하는 오픈소스 프로젝트입니다. 현재는 Windows 데스크톱과 모바일 웹을 우선 채널로 두고, Android를 병렬 확장하고 있습니다. + +## 왜 Windows를 먼저 만드나요 + +이 프로젝트의 강점이 큰 화면에서 드러나는 검색, 복귀, 다중 창, 후속조치 흐름에 있기 때문입니다. 모바일보다 데스크톱에서 생산성 차이가 더 분명하게 드러난다고 판단했습니다. + +## 모바일 웹은 어디서 볼 수 있나요 + +[vstalk.phy.kr](https://vstalk.phy.kr)에서 현재 공개 중인 모바일 웹 진입점을 확인할 수 있습니다. + +## 특정 메신저를 그대로 복제하려는 프로젝트인가요 + +아닙니다. 익숙한 구조는 참고하지만, 목표는 더 낮은 피로도와 더 짧은 복귀 흐름입니다. 복제보다 정제에 가깝습니다. + +## 지금 실제로 되는 것은 무엇인가요 + +- Windows 빌드 +- 모바일 웹 라이브 진입 +- 기본 계정 생성과 세션 유지 +- 대화 목록, 메시지 전송, 읽기 반영 +- 검색/보관/빈 상태 1차 UX + +정확한 범위는 [PROJECT_STATUS.md](PROJECT_STATUS.md)에서 확인할 수 있습니다. + +## 가입 방식은 어떻게 가나요 + +현재 알파 단계에서는 통제된 접근 정책을 쓰고 있지만, 공개 기획 기준은 `초대코드 중심`보다 `이메일 또는 휴대폰 기반 1회성 인증` 쪽으로 이동하고 있습니다. 자세한 배경은 [문서/10-signup-onboarding-and-auth-policy.md](문서/10-signup-onboarding-and-auth-policy.md)에 정리했습니다. + +## 다운로드는 어디서 받나요 + +공식 미러와 저장소 릴리즈를 함께 제공합니다. + +- 공식 미러: [download-vstalk.phy.kr](https://download-vstalk.phy.kr) +- 제2 공개 레포: [physia.kr/open-source/projects/public/kotalk](https://physia.kr/open-source/projects/public/kotalk) +- Forge releases: [git.physia.kr/ian/vs-messanger/releases](https://git.physia.kr/ian/vs-messanger/releases) +- GitHub releases: [github.com/werther24601/kotalk/releases](https://github.com/werther24601/kotalk/releases) + +현재 미러 정합성 상태는 [PROJECT_STATUS.md](PROJECT_STATUS.md), 릴리즈 규칙은 [RELEASING.md](RELEASING.md)에 기록합니다. + +## 공식 서비스와 오픈소스 저장소는 같은가요 + +같지 않습니다. 저장소는 오픈소스 코어와 공개 문서를 다루고, 운영 서비스는 별도 표면으로 관리합니다. 이 경계는 [BUSINESS_MODEL.md](BUSINESS_MODEL.md)와 [DEPLOYMENT_MODES.md](DEPLOYMENT_MODES.md)에 설명해 두었습니다. + +## 라이선스는 무엇인가요 + +현재 저장소는 [Apache License 2.0](LICENSE)을 사용합니다. 상표는 별도 정책을 따르므로 [TRADEMARKS.md](TRADEMARKS.md)도 함께 봐야 합니다. + +## 기여는 어떻게 시작하나요 + +[CONTRIBUTING.md](CONTRIBUTING.md), [FIRST_CONTRIBUTION.md](FIRST_CONTRIBUTION.md), [COMMUNITY.md](COMMUNITY.md)를 순서대로 보면 가장 빠릅니다. + +## 보안 이슈는 어디로 알려야 하나요 + +공개 이슈보다 먼저 [SECURITY.md](SECURITY.md)에 적힌 비공개 경로를 사용해 주세요. diff --git a/FIRST_CONTRIBUTION.md b/FIRST_CONTRIBUTION.md new file mode 100644 index 0000000..6f208b3 --- /dev/null +++ b/FIRST_CONTRIBUTION.md @@ -0,0 +1,45 @@ +# First Contribution + +처음 기여한다면 이 문서만 먼저 보면 됩니다. + +## 1. 현재 상태 읽기 + +아래 순서로 읽으면 충돌 없이 맥락을 잡을 수 있습니다. + +1. [README.md](README.md) +2. [PROJECT_STATUS.md](PROJECT_STATUS.md) +3. [ROADMAP.md](ROADMAP.md) + +## 2. 작업 영역 고르기 + +- 문서/정합성: README, 상태표, 문서 링크, 스크린샷 +- UX 개선: 모바일 웹, 데스크톱 사용 흐름 +- 인프라/릴리즈: 배포, 다운로드, 릴리즈 메타데이터 + +## 3. 난이도 가이드 + +- 쉬움: 오탈자, 링크 정리, 상태표 보강, 스크린샷 설명 개선 +- 중간: 모바일 웹 UI, 문서-구현 정합성 보강, 배포 스크립트 개선 +- 어려움: 인증/세션, 릴리즈 파이프라인, 채널 구조 변경 + +## 4. 브랜치 이름 + +- `docs/` +- `feat/` +- `hotfix/` + +상세 규칙은 [BRANCHING_STRATEGY.md](BRANCHING_STRATEGY.md)를 참고하세요. + +## 5. PR 전 마지막 확인 + +- README 또는 상태표 갱신이 필요한가 +- CHANGELOG 반영이 필요한가 +- 스크린샷이 현재 상태와 맞는가 +- 릴리즈/배포 경로에 영향이 있는가 + +## 6. 도움되는 문서 + +- [CONTRIBUTING.md](CONTRIBUTING.md) +- [COMMUNITY.md](COMMUNITY.md) +- [MAINTAINERS.md](MAINTAINERS.md) +- [RELEASING.md](RELEASING.md) diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..011ada0 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,30 @@ +# Governance + +KoTalk는 현재 단일 메인테이너 중심의 공개 알파 프로젝트입니다. +의사결정은 빠르게 하되, 공개 문서가 실제 상태보다 앞서 나가지 않도록 운영합니다. + +## Decision Order + +1. 사용자가 더 빨리 읽고, 답하고, 복귀할 수 있는가 +2. 한국어 UI와 Windows 중심 흐름에 맞는가 +3. 문서와 구현이 같은 방향을 유지하는가 +4. 릴리즈와 배포 표면이 더 단순해지는가 +5. 보안과 운영 리스크를 키우지 않는가 + +## What Needs Review + +- 가입/인증 정책 변경 +- 보안 모델 변경 +- 다운로드 경로 변경 +- UI 정보구조의 큰 이동 +- 공개 상태 문서와 어긋날 수 있는 기능 추가 + +## Public Rules + +- README는 과장 대신 검증된 상태만 적습니다. +- 스크린샷, 릴리즈, CHANGELOG는 가능한 한 같은 시점 감각으로 갱신합니다. +- 보안과 운영은 “현재 적용 범위”와 “남은 과제”를 분리해 씁니다. + +## Maintainer + +현재 운영과 최종 승인 책임은 [MAINTAINERS.md](MAINTAINERS.md)에 적힌 메인테이너가 맡습니다. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-FAQ.md b/LICENSE-FAQ.md new file mode 100644 index 0000000..eb60e68 --- /dev/null +++ b/LICENSE-FAQ.md @@ -0,0 +1,22 @@ +# License FAQ + +## 현재 라이선스는 무엇인가요 + +이 저장소의 코드와 문서는 [Apache License 2.0](LICENSE)을 따릅니다. + +## 왜 Apache-2.0인가요 + +오픈소스 채택 장벽을 낮추면서도, 특허와 기여 범위를 더 분명하게 설명할 수 있기 때문입니다. + +## 상업적으로 써도 되나요 + +코드 라이선스 범위 안에서는 가능합니다. 다만 브랜드, 공식 서비스, 지원 계약은 별도 표면입니다. + +## 기여에 별도 CLA가 필요한가요 + +일반적인 코드와 문서 기여에는 별도 CLA를 요구하지 않습니다. +상표, 로고, 별도 권리 정리가 필요한 자료는 추가 합의가 필요할 수 있습니다. + +## 상표도 같은 라이선스인가요 + +아닙니다. 상표는 [TRADEMARKS.md](TRADEMARKS.md)를 따릅니다. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..593e4f8 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,24 @@ +# Maintainers + +## Current Maintainer + +- Name: `이재협` +- Public handle: `Ian Werther` +- GitHub: [werther24601](https://github.com/werther24601) +- Organization: `PHYSIA` +- Primary contact: [ian@physia.kr](mailto:ian@physia.kr) + +## Maintainer Scope + +- 제품 방향과 공개 저장소 표면 +- 릴리즈와 상태 문서 정합성 +- 보안/배포 정책의 최종 승인 +- Windows, 웹, Android 우선순위 조정 + +## Working Style + +- 실제 사용 흐름 개선을 우선합니다. +- 과장된 약속보다 상태와 근거를 먼저 맞춥니다. +- 큰 방향 변경은 문서와 함께 남깁니다. + +일반 문의는 [SUPPORT.md](SUPPORT.md), 운영 원칙은 [GOVERNANCE.md](GOVERNANCE.md)를 참고하세요. diff --git a/PORTFOLIO_CAPABILITIES.md b/PORTFOLIO_CAPABILITIES.md new file mode 100644 index 0000000..515c3b3 --- /dev/null +++ b/PORTFOLIO_CAPABILITIES.md @@ -0,0 +1,19 @@ +# PHYSIA Portfolio Capabilities + +이 문서는 KoTalk 저장소가 보여 주는 PHYSIA의 엔지니어링 역량을 정리합니다. + +## What This Repository Demonstrates + +| Capability | Evidence in this repository | +|---|---| +| Product design | 한국어 UI, 낮은 피로도, 데스크톱 생산성 중심의 제품 방향 | +| Multi-platform planning | Windows, 모바일 웹, Android 병렬 전략 | +| Backend implementation | `ASP.NET Core`, 인증, 세션, 실시간 연결, 데이터 저장 | +| Desktop client delivery | `.NET` 기반 데스크톱 셸과 멀티 윈도우 구조 | +| Release operations | 스크린샷, CHANGELOG, 릴리즈 메타데이터, 다운로드 표면 정합성 | +| Security posture | 보안 정책, 제보 경로, 데이터 경계, 릴리즈 무결성 문서화 | +| Self-hosting readiness | 공개 배포 골격, 운영 경계, 배포 모드 설명 | + +## Public Position + +KoTalk는 단순한 데모가 아니라, 제품 기획과 구현, 운영 표면, 보안 설명 가능성을 함께 보여 주는 저장소를 목표로 합니다. diff --git a/PRIVACY_AND_DATA_HANDLING.md b/PRIVACY_AND_DATA_HANDLING.md new file mode 100644 index 0000000..1e59f87 --- /dev/null +++ b/PRIVACY_AND_DATA_HANDLING.md @@ -0,0 +1,26 @@ +# Privacy And Data Handling + +이 문서는 현재 구현 기준에서 어떤 데이터가 어떤 경계에 놓이는지 설명합니다. + +## Current Data Categories + +| Category | Examples | Current purpose | +|---|---|---| +| Account identity | 표시 이름, 사용자 ID | 계정 식별 | +| Device identity | install ID, device name, app version | 세션 구분 | +| Session data | session ID, token family, refresh token hash | 인증과 세션 회전 | +| Conversation data | 대화 제목, 멤버, 메시지 본문, 읽기 커서 | 메시징 기능 | +| Operational logs | 오류, 상태, 제한적 진단 정보 | 장애 분석과 보안 대응 | + +## Handling Principles + +- refresh token 원문은 서버 저장소에 평문으로 남기지 않습니다. +- 메시지 본문과 민감정보는 로그 기본값으로 남기지 않습니다. +- 운영자가 접근할 수 있는 데이터 범위는 최소화하는 방향으로 설계합니다. + +## Current Gaps + +- 정식 개인정보 처리방침 수준의 법률 문서화는 아직 아닙니다. +- 보존 기간, 삭제 절차, 접근 감사는 더 구체화가 필요합니다. + +문의: [help@physia.kr](mailto:help@physia.kr) diff --git a/PROCUREMENT_READINESS.md b/PROCUREMENT_READINESS.md new file mode 100644 index 0000000..f101174 --- /dev/null +++ b/PROCUREMENT_READINESS.md @@ -0,0 +1,24 @@ +# Procurement Readiness + +이 문서는 KoTalk가 규제 환경과 기관 검토에서 어떤 준비 상태에 있는지 과장 없이 정리합니다. + +## Current Position + +현재 단계는 `공개 검토 가능` 수준의 알파입니다. 즉시 조달 가능 제품으로 주장하지 않으며, 준비된 항목과 남은 항목을 구분합니다. + +## Snapshot + +| Area | Current read | +|---|---| +| 공개 문서와 상태표 | 부분 준비 | +| 보안 정책과 제보 경로 | 부분 준비 | +| 자체 호스팅 방향 | 부분 준비 | +| 폐쇄망 설치 검증 | 미검증 | +| 감사 로그와 운영 증빙 | 미완성 | +| 백업/복구 절차 | 미완성 | + +## Principle + +- 지원 가능성과 현재 제공 중인 범위를 분리해서 씁니다. +- 규제 환경 대응은 마케팅 문구보다 운영 증빙이 먼저입니다. +- 검증 전 항목은 준비 방향으로만 기록합니다. diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md new file mode 100644 index 0000000..4894517 --- /dev/null +++ b/PROJECT_STATUS.md @@ -0,0 +1,114 @@ +# Project Status + +마지막 검증일: `2026-04-16` + +## Status Dashboard + +| Signal | Current read | +|---|---| +| Public brand | `KoTalk` | +| Stage | `Alpha` | +| Most usable surface | Mobile web live + Windows build | +| Biggest current gap | Android 실빌드와 다운로드 미러 정합성 | +| Signup direction | 공개형 1회성 인증 중심으로 재설계 중 | +| Tone of this repo | 현재 동작 범위와 남은 갭을 함께 적는 제품형 저장소 | + +## What Exists Right Now + +KoTalk는 아직 모든 플랫폼이 완성된 상태는 아니지만, “문서만 있는 프로젝트” 단계는 이미 지났습니다. 현재 저장소 기준으로 아래 항목을 실제로 확인할 수 있습니다. + +- Windows 데스크톱 클라이언트 빌드 +- 모바일 웹 실서비스 채널 +- 기본 인증, 최근 대화, 메시지 전송, 읽기 커서, 세션 복구 루프 +- 최신 기준 스크린샷 세트 +- 릴리즈 경로와 다운로드 경로 문서 + +## Channel Status + +| Channel | Surface | Status | Notes | +|---|---|---|---| +| Windows desktop | 저장소 빌드 / 릴리즈 산출물 | Buildable | 핵심 메시징 루프 검증 가능 | +| Mobile web | [vstalk.phy.kr](https://vstalk.phy.kr) | Live | 가입, 대화, 검색, 보관 1차 흐름 제공 | +| Android | 저장소 릴리즈 예정 | In progress | 문서와 배포 구조 우선 정리 중 | +| Official mirror | [download-vstalk.phy.kr](https://download-vstalk.phy.kr) | Normalizing | 2026-04-16 기준 DNS/HTTPS 정합성 확인 필요 | + +## Verified Now + +현재 기준으로 확인된 사실만 적습니다. + +- Windows 클라이언트는 저장소 기준으로 빌드 가능한 상태입니다. +- 모바일 웹은 [vstalk.phy.kr](https://vstalk.phy.kr)에서 공개 중입니다. +- 기본 메시징 루프와 세션 복구 흐름은 구현돼 있습니다. +- 검색, 보관, 빈 상태 UX는 1차 개편이 반영돼 있습니다. +- 최신 스크린샷은 저장소에 함께 보관됩니다. + +## Visual Proof + +| Surface | Proof | +|---|---| +| Desktop shell | [hero-shell.png](docs/assets/latest/hero-shell.png) | +| Desktop onboarding | [onboarding.png](docs/assets/latest/onboarding.png) | +| Desktop conversation | [conversation.png](docs/assets/latest/conversation.png) | +| Mobile web onboarding | [vstalk-web-onboarding.png](docs/assets/latest/vstalk-web-onboarding.png) | +| Mobile web inbox | [vstalk-web-list.png](docs/assets/latest/vstalk-web-list.png) | +| Mobile web search | [vstalk-web-search.png](docs/assets/latest/vstalk-web-search.png) | +| Mobile web saved | [vstalk-web-saved.png](docs/assets/latest/vstalk-web-saved.png) | +| Mobile web chat | [vstalk-web-chat.png](docs/assets/latest/vstalk-web-chat.png) | + +## Product Direction That Is Already Visible + +현재 화면만 봐도 읽히는 제품 방향은 아래와 같습니다. + +- 메시징을 중심에 두고, 피드형 잡음을 덜어내려는 구조 +- 텍스트를 장황하게 읽게 하기보다 구조와 위치로 이해시키는 UI +- 한국어 데스크톱 사용성, 특히 반복적인 읽기와 답장 흐름을 중시하는 설계 +- 단순한 “보여주기용 스크린샷”이 아니라 실제 릴리즈와 상태 문서에 연결된 표면 + +## In Progress + +- Android 첫 실사용 빌드 +- 공개 다운로드 미러 정합성 +- 릴리즈 페이지와 미러 간 latest 라우트 통합 +- 검색 범위 확장 +- 파일 전송 +- 데스크톱 멀티 윈도우 생산성 강화 + +## Current Limits + +아직 부족한 부분도 그대로 남깁니다. + +- Android 실사용 빌드는 아직 제공되지 않습니다. +- 파일 전송은 미구현입니다. +- 검색은 전역 파일/링크/사람 범위까지 확장되지 않았습니다. +- 공식 다운로드 미러는 DNS/HTTPS 정상화가 끝나야 안정 채널로 표기할 수 있습니다. +- 데스크톱 멀티 윈도우는 방향은 잡혀 있지만, 실제 생산성 흐름은 더 다듬어야 합니다. + +## Download And Release Paths + +| Path | Purpose | +|---|---| +| [download-vstalk.phy.kr](https://download-vstalk.phy.kr) | 공식 다운로드 미러 주소 | +| [download-vstalk.phy.kr/windows/latest](https://download-vstalk.phy.kr/windows/latest) | Windows latest | +| [download-vstalk.phy.kr/android/latest](https://download-vstalk.phy.kr/android/latest) | Android latest | +| [download-vstalk.phy.kr/latest/version.json](https://download-vstalk.phy.kr/latest/version.json) | 버전 메타데이터 | +| [physia.kr/open-source/projects/public/kotalk](https://physia.kr/open-source/projects/public/kotalk) | 제2 공개 레포 | +| [Forge releases](https://git.physia.kr/ian/vs-messanger/releases) | 저장소 릴리즈 채널 | +| [GitHub releases](https://github.com/werther24601/kotalk/releases) | 공개 릴리즈 채널 | + +## Why This Repo May Feel Denser Again + +최근 공개 표면은 리스크를 줄이려는 과정에서 지나치게 건조해졌습니다. 현재는 다시 아래 균형을 맞추는 방향으로 조정 중입니다. + +- 화면과 스크린샷은 충분히 보여 주되, 과장된 약속은 줄이기 +- 제품 배경과 문제의식은 다시 설명하되, 감정적인 공격은 피하기 +- 상태 문서는 짧게 유지하되, 이 저장소가 왜 존재하는지는 읽히게 만들기 + +배경 문서는 [BACKGROUND.md](BACKGROUND.md), 더 긴 맥락은 [문서/14-project-background-and-market-context.md](문서/14-project-background-and-market-context.md)에서 확인할 수 있습니다. + +## Review Focus + +- 사용자 관점 리뷰: [문서/31-user-review-log-and-experience-scorecard.md](문서/31-user-review-log-and-experience-scorecard.md) +- 현재 모바일 웹 리뷰: [문서/89-current-product-mobile-web-review-2026-04.md](문서/89-current-product-mobile-web-review-2026-04.md) +- 라이브 우선순위: [문서/35-live-user-review-and-priority-backlog.md](문서/35-live-user-review-and-priority-backlog.md) +- 가입/온보딩 정책: [문서/10-signup-onboarding-and-auth-policy.md](문서/10-signup-onboarding-and-auth-policy.md) +- 신뢰와 보안 표면: [TRUST_CENTER.md](TRUST_CENTER.md), [SECURITY.md](SECURITY.md) diff --git a/PhysOn.sln b/PhysOn.sln new file mode 100644 index 0000000..2e37bd4 --- /dev/null +++ b/PhysOn.sln @@ -0,0 +1,78 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BD1AEF6A-5B59-4DAC-82C9-1983916200FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Api", "src\PhysOn.Api\PhysOn.Api.csproj", "{DD8A5885-9647-4248-A0B2-C8695BBFB54E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Domain", "src\PhysOn.Domain\PhysOn.Domain.csproj", "{53C0FE48-1759-46FC-B61D-56AD0B60941E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Contracts", "src\PhysOn.Contracts\PhysOn.Contracts.csproj", "{737314E6-E8E6-43BD-8806-06B82C565A85}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Infrastructure", "src\PhysOn.Infrastructure\PhysOn.Infrastructure.csproj", "{F13AF8BF-92CF-444B-B410-1BFA38DAD0CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Application", "src\PhysOn.Application\PhysOn.Application.csproj", "{A3D77E81-11B9-4C51-872B-28DABEB6F665}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Worker", "src\PhysOn.Worker\PhysOn.Worker.csproj", "{925D92EC-965D-44D9-A3FF-011E1815C9EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Desktop", "src\PhysOn.Desktop\PhysOn.Desktop.csproj", "{90EF510F-E338-45C4-9EF4-0A4916725EF0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{04EBE1E2-D374-4F58-A0D5-5062BB4674FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhysOn.Api.IntegrationTests", "tests\PhysOn.Api.IntegrationTests\PhysOn.Api.IntegrationTests.csproj", "{38821CD9-915B-4C96-8A35-11BDE991C16E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DD8A5885-9647-4248-A0B2-C8695BBFB54E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD8A5885-9647-4248-A0B2-C8695BBFB54E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD8A5885-9647-4248-A0B2-C8695BBFB54E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD8A5885-9647-4248-A0B2-C8695BBFB54E}.Release|Any CPU.Build.0 = Release|Any CPU + {53C0FE48-1759-46FC-B61D-56AD0B60941E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53C0FE48-1759-46FC-B61D-56AD0B60941E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53C0FE48-1759-46FC-B61D-56AD0B60941E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53C0FE48-1759-46FC-B61D-56AD0B60941E}.Release|Any CPU.Build.0 = Release|Any CPU + {737314E6-E8E6-43BD-8806-06B82C565A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {737314E6-E8E6-43BD-8806-06B82C565A85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {737314E6-E8E6-43BD-8806-06B82C565A85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {737314E6-E8E6-43BD-8806-06B82C565A85}.Release|Any CPU.Build.0 = Release|Any CPU + {F13AF8BF-92CF-444B-B410-1BFA38DAD0CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F13AF8BF-92CF-444B-B410-1BFA38DAD0CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F13AF8BF-92CF-444B-B410-1BFA38DAD0CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F13AF8BF-92CF-444B-B410-1BFA38DAD0CB}.Release|Any CPU.Build.0 = Release|Any CPU + {A3D77E81-11B9-4C51-872B-28DABEB6F665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3D77E81-11B9-4C51-872B-28DABEB6F665}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3D77E81-11B9-4C51-872B-28DABEB6F665}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3D77E81-11B9-4C51-872B-28DABEB6F665}.Release|Any CPU.Build.0 = Release|Any CPU + {925D92EC-965D-44D9-A3FF-011E1815C9EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {925D92EC-965D-44D9-A3FF-011E1815C9EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {925D92EC-965D-44D9-A3FF-011E1815C9EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {925D92EC-965D-44D9-A3FF-011E1815C9EE}.Release|Any CPU.Build.0 = Release|Any CPU + {90EF510F-E338-45C4-9EF4-0A4916725EF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90EF510F-E338-45C4-9EF4-0A4916725EF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90EF510F-E338-45C4-9EF4-0A4916725EF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90EF510F-E338-45C4-9EF4-0A4916725EF0}.Release|Any CPU.Build.0 = Release|Any CPU + {38821CD9-915B-4C96-8A35-11BDE991C16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38821CD9-915B-4C96-8A35-11BDE991C16E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38821CD9-915B-4C96-8A35-11BDE991C16E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38821CD9-915B-4C96-8A35-11BDE991C16E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {DD8A5885-9647-4248-A0B2-C8695BBFB54E} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {53C0FE48-1759-46FC-B61D-56AD0B60941E} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {737314E6-E8E6-43BD-8806-06B82C565A85} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {F13AF8BF-92CF-444B-B410-1BFA38DAD0CB} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {A3D77E81-11B9-4C51-872B-28DABEB6F665} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {925D92EC-965D-44D9-A3FF-011E1815C9EE} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {90EF510F-E338-45C4-9EF4-0A4916725EF0} = {BD1AEF6A-5B59-4DAC-82C9-1983916200FB} + {38821CD9-915B-4C96-8A35-11BDE991C16E} = {04EBE1E2-D374-4F58-A0D5-5062BB4674FA} + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..962abde --- /dev/null +++ b/README.md @@ -0,0 +1,190 @@ +# KoTalk + +

+ 한국어 중심의 차분한 메시징 경험을 다시 설계하는 오픈소스 프로젝트. +

+ +

+ Windows 데스크톱을 중심에 두고, 모바일 웹과 Android를 병렬 확장하는 메신저 저장소입니다. +

+ +

+ 짧은 답장, 빠른 복귀, 설명 가능한 제품 표면, 그리고 한국어 사용 습관에 맞는 조용한 UI를 핵심 기준으로 삼습니다. +

+ +

+ status + platforms + docs + license + web + download + verified +

+ +

+ Project Status · + Showcase · + Background · + FAQ · + Releases · + Trust Center · + Architecture · + Master Plan · + Contributing +

+ + + + + + + + +
Try Web
모바일 웹 진입점
See Screens
최신 화면 묶음
Read Status
현재 동작 범위
Get Builds
미러와 릴리즈 경로
+ +

+ KoTalk desktop shell +

+

+ 플랫한 화이트 톤과 컴팩트한 창 밀도를 기준으로 정리한 현재 데스크톱 셸 +

+ +## Snapshot + +| Layer | Current read | +|---|---| +| What this is | 한국어 UI, 낮은 피로도, 업무형 메시징 복귀 흐름을 중심에 둔 Windows-first 오픈소스 메신저 | +| What works now | Windows 빌드, 모바일 웹 라이브, 기본 인증/대화/세션 루프 | +| What is still moving | Android 첫 실빌드, 파일 전송, 검색 확장, 공개 다운로드 미러 정합성 | +| What this repo tries to show | 화면, 빌드 산출물, 릴리즈 경로, 상태 문서, 배경 문서를 한 눈에 읽히게 정리한 제품형 저장소 | + +## Why Now + +KoTalk는 단순히 새로운 메신저를 하나 더 만드는 시도가 아닙니다. 최근 몇 년간 국내 메신저 여론에서 반복적으로 드러난 피로는 대체로 비슷했습니다. 대화보다 콘텐츠가 먼저 보이는 구조, 설명이 늦는 운영정책, 신뢰를 깎는 개인정보 이슈, 반복 장애에 대한 피로감이 그것입니다. + +이 저장소는 그 불만을 과격하게 소비하려는 프로젝트가 아니라, 더 조용하고 더 짧고 더 예측 가능한 한국어 메시징 경험을 다시 설계해 보려는 제안입니다. 익숙한 메신저 문법은 존중하되, 반복해서 확인하는 목록, 답장을 미루지 않게 돕는 흐름, 업무적 대화와 사적 대화가 서로 피로를 만들지 않는 구조를 우선합니다. + +특히 최근 공개 기사와 여론에서 반복적으로 읽히는 네 가지 신호를 중요한 배경으로 봅니다. + +- 친구 탭 피드화, 숏폼, 광고 노출 확대에 대한 강한 UI 피로 +- 개인정보와 프라이버시 이슈 이후 높아진 기본 보안 기대 +- 운영정책 강화가 있을 때 더 크게 요구되는 설명 책임 +- PC 로그인과 메시지 전송 장애가 반복될 때 누적되는 불신 + +KoTalk는 이 배경을 리스크 문구로 숨기지 않고, 왜 이 프로젝트가 필요한지 설명하는 핵심 맥락으로 다룹니다. 배경 요약은 [BACKGROUND.md](BACKGROUND.md), 더 긴 맥락은 [문서/14-project-background-and-market-context.md](문서/14-project-background-and-market-context.md)에 정리돼 있습니다. + +## What Makes KoTalk Different + +| Focus | KoTalk approach | +|---|---| +| Desktop priority | Windows를 중심축으로 두고, 넓은 화면에서 짧은 클릭 수와 멀티 윈도우 흐름을 우선합니다. | +| UI mood | 각진 패널, 얇은 보더, 플랫한 화이트 톤, 설명보다 구조가 먼저 보이는 화면을 지향합니다. | +| Work communication | 검색, 보관, 후속조치, 복귀 시간을 줄이는 흐름을 제품 핵심으로 둡니다. | +| Transparency | 상태 문서, 릴리즈 경로, 스크린샷, 현재 한계를 함께 적습니다. | +| Deployment choice | 공개 서비스와 자체 호스팅 가능한 오픈소스 코어를 구분해 설명합니다. | + +## Current Experience Shelf + +현재 저장소에서 바로 볼 수 있는 화면과 산출물은 아래와 같습니다. + +| Surface | What to look at | Visual | +|---|---|---| +| Desktop shell | 레일 + 목록 + 대화 중심의 3단 구조, 플랫한 보더, 멀티 윈도우 전제 | [hero-shell.png](docs/assets/latest/hero-shell.png) | +| Desktop onboarding | 첫 실행 시 서버 주소보다 사용자 흐름을 먼저 보여주는 가벼운 진입 | [onboarding.png](docs/assets/latest/onboarding.png) | +| Desktop conversation | 메시지 흐름, 읽기 상태, 입력 패널의 조밀한 배치 | [conversation.png](docs/assets/latest/conversation.png) | +| Mobile web onboarding | 빠른 진입과 한국어 중심의 간결한 가입 흐름 | [vstalk-web-onboarding.png](docs/assets/latest/vstalk-web-onboarding.png) | +| Mobile web inbox | 최근 대화, 필터, 검색 진입의 기본 구조 | [vstalk-web-list.png](docs/assets/latest/vstalk-web-list.png) | +| Mobile web search | 대화 재발견과 보관 흐름의 1차 구현 | [vstalk-web-search.png](docs/assets/latest/vstalk-web-search.png) | +| Mobile web saved | 나중에 답장, 중요 대화, 다시 열기 허브 | [vstalk-web-saved.png](docs/assets/latest/vstalk-web-saved.png) | +| Mobile web chat | 모바일 입력창, 상단 정보 밀도, 복귀 동선 | [vstalk-web-chat.png](docs/assets/latest/vstalk-web-chat.png) | + +전체 화면 묶음은 [SHOWCASE.md](SHOWCASE.md)에서 더 자세히 볼 수 있습니다. + +## Channels + +| Channel | Surface | Status | Notes | +|---|---|---|---| +| Windows desktop | 저장소 빌드와 버전별 산출물 | Buildable | 핵심 메시징 루프와 데스크톱 레이아웃 실험 진행 중 | +| Mobile web | [vstalk.phy.kr](https://vstalk.phy.kr) | Live | 가입, 대화, 검색, 보관 1차 흐름 검증 | +| Android | 저장소 릴리즈 예정 | In progress | 문서와 배포 구조 우선 정리 중 | +| Official download mirror | [download-vstalk.phy.kr](https://download-vstalk.phy.kr) | Normalizing | 2026-04-16 기준 DNS/HTTPS 정합성 점검 진행 중 | + +## Architecture Snapshot + +KoTalk의 현재 구조는 지나치게 복잡한 플랫폼보다, 작은 조각을 조합해 실서비스와 로컬 빌드를 함께 검증하는 쪽에 가깝습니다. + +- 클라이언트: Windows 데스크톱 + 모바일 웹 + Android 예정 +- API: 인증, 최근 대화, 메시지 전송, 읽기 커서, 세션 루프 +- 배포: VPS 기반 same-origin 웹앱과 API 운영 +- 공개 증거: 저장소 스크린샷, 빌드 산출물, 상태 문서, 릴리즈 경로 + +자세한 구성은 [ARCHITECTURE.md](ARCHITECTURE.md), 배포 모드는 [DEPLOYMENT_MODES.md](DEPLOYMENT_MODES.md), 로드맵은 [ROADMAP.md](ROADMAP.md)에서 확인할 수 있습니다. + +## Download Paths + +공식 링크 하나에만 의존하지 않고, 저장소 릴리즈 경로를 함께 제공합니다. + +| Path | Link | +|---|---| +| Official mirror | [download-vstalk.phy.kr](https://download-vstalk.phy.kr) | +| Windows latest | [download-vstalk.phy.kr/windows/latest](https://download-vstalk.phy.kr/windows/latest) | +| Android latest | [download-vstalk.phy.kr/android/latest](https://download-vstalk.phy.kr/android/latest) | +| Version manifest | [download-vstalk.phy.kr/latest/version.json](https://download-vstalk.phy.kr/latest/version.json) | +| Public stage repo | [physia.kr/open-source/projects/public/kotalk](https://physia.kr/open-source/projects/public/kotalk) | +| Forge releases | [git.physia.kr/ian/vs-messanger/releases](https://git.physia.kr/ian/vs-messanger/releases) | +| GitHub releases | [github.com/werther24601/kotalk/releases](https://github.com/werther24601/kotalk/releases) | + +릴리즈 정책과 현재 미러 상태는 [RELEASING.md](RELEASING.md), [PROJECT_STATUS.md](PROJECT_STATUS.md)에 함께 기록합니다. + +## Principles + +- 한국어 UI는 번역체보다 실제 사용 흐름을 우선합니다. +- 둥글고 장식적인 UI보다 각진 구조, 플랫한 깊이, 짧은 텍스트, 높은 밀도를 택합니다. +- 개인 대화와 업무형 소통 모두에서 검색, 복귀, 정리, 후속조치가 더 짧아져야 합니다. +- 공식 서비스와 오픈소스 코어, 저장소 공개 표면은 같은 문장으로 뭉개지지 않게 설명합니다. +- 보안은 과장된 문구보다 현재 적용 범위와 남은 과제를 함께 적는 방식으로 다룹니다. + +## Reading Paths + +처음 보는 독자라면 아래 순서가 가장 빠릅니다. + +1. [PROJECT_STATUS.md](PROJECT_STATUS.md) +2. [SHOWCASE.md](SHOWCASE.md) +3. [BACKGROUND.md](BACKGROUND.md) +4. [FAQ.md](FAQ.md) +5. [문서/README.md](문서/README.md) + +기여 관점이라면 [CONTRIBUTING.md](CONTRIBUTING.md), [COMMUNITY.md](COMMUNITY.md), [DEVELOPMENT.md](DEVELOPMENT.md)를 먼저 보면 됩니다. + +제품 방향을 더 길게 읽고 싶다면 아래 문서를 권합니다. + +- [ROADMAP.md](ROADMAP.md) +- [BUSINESS_MODEL.md](BUSINESS_MODEL.md) +- [문서/01-product-strategy-and-mvp.md](문서/01-product-strategy-and-mvp.md) +- [문서/18-white-material-compact-ui-system.md](문서/18-white-material-compact-ui-system.md) +- [문서/22-work-communication-ux-playbook.md](문서/22-work-communication-ux-playbook.md) + +## Security And Trust + +공개 저장소에서 약속하는 범위는 아래 문서에 나눠 적습니다. + +- [TRUST_CENTER.md](TRUST_CENTER.md): 현재 통제, 남은 갭, 표현 원칙 +- [SECURITY.md](SECURITY.md): 제보 경로와 기본 보안 정책 +- [SECURITY_RESPONSE.md](SECURITY_RESPONSE.md): 접수와 공개 절차 +- [PRIVACY_AND_DATA_HANDLING.md](PRIVACY_AND_DATA_HANDLING.md): 현재 데이터 경계 +- [DEPLOYMENT_MODES.md](DEPLOYMENT_MODES.md): 공식 서비스, 셀프호스팅, 규제 환경 배포 구분 + +## Open-Source And Service Boundary + +KoTalk는 저장소 공개 코드와 운영 서비스의 경계를 분명히 둡니다. + +- 이 저장소는 Apache-2.0 기반의 오픈소스 코어와 공개 문서를 다룹니다. +- 운영 서비스와 배포 지원은 별도 표면으로 설명합니다. +- 자체 호스팅과 향후 사설망/폐쇄망 배포 가능성은 중요한 방향이지만, 검증 전 항목은 검증 완료처럼 쓰지 않습니다. + +자세한 구조는 [BUSINESS_MODEL.md](BUSINESS_MODEL.md), [DEPLOYMENT_MODES.md](DEPLOYMENT_MODES.md), [PROCUREMENT_READINESS.md](PROCUREMENT_READINESS.md)에 정리해 두었습니다. + +## Maintained By + +KoTalk는 현재 PHYSIA가 유지하고 있습니다. 실무 연락은 [SUPPORT.md](SUPPORT.md), 운영 모델은 [GOVERNANCE.md](GOVERNANCE.md), 메인테이너 정보는 [MAINTAINERS.md](MAINTAINERS.md)를 참고하세요. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..99e457e --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,38 @@ +# Releasing + +KoTalk의 릴리즈는 단순한 파일 업로드가 아니라, 산출물과 공개 문서가 같은 상태를 가리키도록 맞추는 작업입니다. + +## Release Surfaces + +- 공식 다운로드 미러: [download-vstalk.phy.kr](https://download-vstalk.phy.kr) +- Windows latest: [download-vstalk.phy.kr/windows/latest](https://download-vstalk.phy.kr/windows/latest) +- Android latest: [download-vstalk.phy.kr/android/latest](https://download-vstalk.phy.kr/android/latest) +- 버전 메타데이터: [download-vstalk.phy.kr/latest/version.json](https://download-vstalk.phy.kr/latest/version.json) +- 제2 공개 레포: [physia.kr/open-source/projects/public/kotalk](https://physia.kr/open-source/projects/public/kotalk) +- Forge releases: [git.physia.kr/ian/vs-messanger/releases](https://git.physia.kr/ian/vs-messanger/releases) +- GitHub releases: [github.com/werther24601/kotalk/releases](https://github.com/werther24601/kotalk/releases) + +## Current Note + +2026-04-16 기준 [download-vstalk.phy.kr](https://download-vstalk.phy.kr)의 DNS/HTTPS 정합성은 재점검이 필요합니다. +그래서 현재는 저장소 릴리즈 경로를 함께 유지하는 것을 원칙으로 둡니다. + +## Minimum Release Contract + +1. 실제로 실행 가능한 산출물이 있어야 합니다. +2. [README.md](README.md)와 [PROJECT_STATUS.md](PROJECT_STATUS.md)가 같은 상태를 가리켜야 합니다. +3. [CHANGELOG.md](CHANGELOG.md)에 의미 있는 변경이 기록돼야 합니다. +4. 최신 스크린샷이 현재 UI를 대표해야 합니다. +5. 다운로드 경로와 릴리즈 링크가 함께 갱신돼야 합니다. + +## Platform Policy + +- Windows: 빌드 산출물, 스크린샷, 체크섬을 함께 남깁니다. +- Mobile web: 라이브 반영이 있으면 스크린샷과 상태 문서를 함께 갱신합니다. +- Android: APK 공개 시 공식 미러와 저장소 릴리즈를 함께 맞춥니다. + +## Related Docs + +- 배포 골격: [deploy/README.md](deploy/README.md) +- 릴리즈 메타데이터: [release-assets/README.md](release-assets/README.md) +- 상태표: [PROJECT_STATUS.md](PROJECT_STATUS.md) diff --git a/REPOSITORY_LAYOUT.md b/REPOSITORY_LAYOUT.md new file mode 100644 index 0000000..ef85aae --- /dev/null +++ b/REPOSITORY_LAYOUT.md @@ -0,0 +1,41 @@ +# Repository Layout + +이 저장소는 코드, 공개 보조 문서, 상세 기획 문서를 역할별로 나눠 관리합니다. + +## Top Level + +- `src/`: 제품 코드 +- `tests/`: 자동 테스트 +- `docs/`: 공개 저장소 보조 문서와 시각 자산 +- `문서/`: 제품 마스터 플랜과 UX 아틀라스 +- `deploy/`: 범용 배포 골격 +- `release-assets/`: 릴리즈 메타데이터와 스테이징 자산 +- `scripts/release/`: 릴리즈 준비와 게시 스크립트 +- `scripts/deploy/`: 서버와 웹앱 배포 스크립트 +- `scripts/ci/`: 캡처와 검증 보조 스크립트 +- `.workspace-*`: 워크스페이스 전용 비공개 정책과 시크릿 + +## Public Naming + +- 제품 노출명: `KoTalk` +- 한글 표기: `코톡` +- 웹 진입점: `vstalk.phy.kr` +- 다운로드 미러: `download-vstalk.phy.kr` + +## Technical Note + +현재 코드 네임스페이스와 프로젝트 파일은 여전히 `PhysOn.*`를 사용합니다. +공개 브랜드와 소스 네이밍은 단계적으로 정렬합니다. + +## Documentation Roles + +- `docs/assets/`: README와 공개 표면에 직접 쓰는 이미지와 SVG +- `docs/archive/`: 보관용 초안 +- `docs/repository-surfaces.md`: 공개 표면 규칙 +- `문서/`: 제품 전략, UX 기준, 실행 계획 + +## House Rules + +- 공개 문서에는 비밀값, 운영 힌트, 내부 메모를 남기지 않습니다. +- 공개 자산과 보관용 초안은 같은 폴더에 섞지 않습니다. +- 공식 스크립트 경로는 `scripts/release`, `scripts/deploy`, `scripts/ci`입니다. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..dae7837 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,94 @@ +# Roadmap + +## 현재 상태 + +- Alpha 가입, 대화 목록, 대화창, 텍스트 전송이 동작하는 첫 사용 가능 프로토타입 확보 +- Windows x64 portable build 생성 가능 +- `vstalk.phy.kr` 모바일 웹앱과 API를 VPS에 실제 배포 +- 원격 저장소에 최신 기준 스크린샷 포함 시작 +- `vstalk.phy.kr` 모바일 웹앱 MVP 빌드 및 same-origin API 검증 완료 + +## v0.1 Alpha + +- [x] 초대코드 기반 가입 +- [x] 최근 대화 로드 +- [x] 메시지 전송 +- [x] 읽기 커서 갱신 +- [x] Windows portable zip 생성 +- [x] 모바일 웹앱 PWA 셸/가입/대화/전송 +- [x] VPS 공개 API 상시 구동 +- [x] `vstalk.phy.kr` same-origin 웹앱 배포 +- [ ] 데스크톱 WebSocket 실시간 반영 + +## v0.2 Collaboration Basics + +- [ ] 파일 전송 +- [ ] 메시지 검색 +- [ ] 고정 / 읽지 않음 / 보관 UX +- [ ] 알림/트레이 고도화 +- [ ] 공개 다운로드 채널 개통 + +## v0.2 Android First-class + +- [ ] Android 셸/네비게이션 골격 +- [ ] Android 로그인/대화 목록/대화 진입 MVP +- [ ] APK 서명 및 산출물 규칙 확정 +- [ ] Windows/Android 동시 릴리즈 메타데이터 검증 +- [ ] Forge Releases + VPS 미러 동시 게시 + +## v0.2 Mobile Web Entry + +- [x] `vstalk.phy.kr` 모바일 웹 IA와 핵심 사용자 흐름 확정 +- [x] 링크 진입 화면과 초간단 가입/로그인 구조 정리 +- [x] 최근 대화 목록과 대화 화면 모바일 MVP 구현 +- [x] 업무/친근 소통 공존 규칙과 활동 화면 우선순위 문서화 +- [x] PWA 도입 전제와 비포함 범위 문서화 +- [x] 실제 VPS 배포 및 same-origin API 검증 +- [ ] 웹앱 전용 최신 스크린샷 갱신 자동화 + +## v0.3 Reliability + +- [ ] 재연결 UX 고도화 +- [ ] 로컬 Draft/캐시 안정화 +- [ ] 자동 업데이트 전략 수립 +- [ ] 배포 자동화와 릴리즈 검증 강화 +- [ ] 최신 기준 스크린샷 갱신 자동 체크리스트 정착 + +## v1.0 Preview + +- [ ] 다중 기기 세션 관리 +- [ ] Passkey 또는 더 강한 로그인 흐름 +- [ ] 업무/개인 맥락 전환 UX +- [ ] 장기 보관과 검색 품질 개선 + +## 사업화와 조달 대응 방향 + +- [ ] `오픈소스 코어 / 공식 플랫폼 / 기관 대응 기능` 경계 문서화 +- [ ] 감사 로그, 관리자 정책, 조직 계정 모델의 최소 골격 확보 +- [ ] 셀프호스팅 배포 문서와 관리형 플랫폼 운영 문서 분리 +- [ ] 접근성, 릴리즈 증빙, 배포 선택권을 공공/기관 대응 기준으로 축적 +- [ ] 크라우드 펀딩용 공개 표면과 릴리즈/상태표 정합성 유지 + +## 보류 또는 실험 항목 + +- 음성/영상 통화 +- 공개 커뮤니티 +- 결제/송금 +- 피드형 콘텐츠 +- 과도한 자동 보조 기능 탑재 + +## 기여 우선순위 + +1. Android 최소 런칭 패스 구축 +2. 데스크톱 실시간 동기화 +3. 파일 전송과 검색 +4. Forge Releases와 다운로드 미러 정합성 +5. Android 첫 배포 이후 릴리즈 자동화 + +## 멀티 OS 릴리즈 운영 규칙 + +- 하나의 태그는 하나의 릴리즈 레코드를 뜻합니다. +- 같은 버전 번호 아래에 Windows와 Android 자산을 함께 게시합니다. +- 원격 저장소에는 최신 기준 스크린샷도 함께 포함합니다. +- 다운로드 미러와 원격 Releases는 같은 자산 이름과 같은 노트를 기준으로 맞춥니다. +- 모바일 웹은 설치형 산출물 대신 `https://vstalk.phy.kr`를 기준 진입점으로 관리합니다. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..2f8068a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,40 @@ +# Security Policy + +KoTalk는 알파 단계의 오픈소스 프로젝트입니다. +완전무결함을 약속하지는 않지만, 공개 문서에서는 현재 통제와 남은 과제를 구분해 설명하는 방식을 유지합니다. + +## Current Principles + +- 기본 비밀값과 임시 접근값은 공개 문서에 실어두지 않습니다. +- 메시지 본문과 민감정보를 로그 기본값으로 남기지 않습니다. +- 세션과 인증 경로는 짧은 수명, 회전, 원격 폐기를 전제로 설계합니다. +- 공식 릴리즈 경로와 무결성 정보는 함께 제공합니다. + +## What This Policy Covers + +- 취약점 제보 경로 +- 현재 적용 중인 기본 통제 +- 아직 남아 있는 보강 과제 + +자세한 신뢰 표면은 [TRUST_CENTER.md](TRUST_CENTER.md), 제보 처리 방식은 [SECURITY_RESPONSE.md](SECURITY_RESPONSE.md)에서 확인할 수 있습니다. + +## Reporting A Vulnerability + +보안 이슈는 공개 이슈 대신 아래 경로로 먼저 알려 주세요. + +- [ian@physia.kr](mailto:ian@physia.kr) +- [contact@physia.kr](mailto:contact@physia.kr) + +가능하면 아래 정보를 포함해 주세요. + +- 영향 범위 +- 재현 절차 +- 예상 시나리오 +- 임시 완화책 + +## Current Areas Still In Progress + +- 브라우저 세션 저장 경계 강화 +- 취약점 advisory 공개 체계 +- 키 회전, 백업/복구, 공급망 보안 증빙 +- 규제 환경용 설치/운영 검증 diff --git a/SECURITY_RESPONSE.md b/SECURITY_RESPONSE.md new file mode 100644 index 0000000..ba8be45 --- /dev/null +++ b/SECURITY_RESPONSE.md @@ -0,0 +1,26 @@ +# Security Response + +이 문서는 보안 이슈 제보를 받았을 때의 기본 대응 방식을 설명합니다. + +## Contact + +- 보안 제보: [ian@physia.kr](mailto:ian@physia.kr) +- 운영 연계: [contact@physia.kr](mailto:contact@physia.kr) + +공개 이슈에는 취약점 세부 내용을 남기지 않는 것을 권장합니다. + +## Response Targets + +| Step | Target | +|---|---| +| Receipt acknowledgement | 영업일 기준 3일 이내 | +| Initial triage | 영업일 기준 5일 이내 | +| Severity update | 재현 여부 확인 후 가능한 한 빠르게 | +| Fix or mitigation guidance | 심각도와 재현 범위에 따라 별도 판단 | + +## Severity Guide + +- `Critical`: 계정 탈취, 원격 실행, 대규모 데이터 노출 +- `High`: 인증 우회, 장기 세션 탈취, 권한 상승, 릴리즈 변조 +- `Medium`: 제한적 정보 노출, 우회 가능한 보호장치 +- `Low`: 설명 부족, 완화 가능한 설정 실수 diff --git a/SHOWCASE.md b/SHOWCASE.md new file mode 100644 index 0000000..12bbf64 --- /dev/null +++ b/SHOWCASE.md @@ -0,0 +1,77 @@ +# Showcase + +KoTalk의 현재 공개 표면을 짧게 훑어보는 문서가 아니라, 지금 저장소에서 실제로 확인할 수 있는 화면과 동작 범위를 빠르게 따라가는 문서입니다. + +## Live Surfaces + +| Surface | Link | What it shows | +|---|---|---| +| Mobile web | [vstalk.phy.kr](https://vstalk.phy.kr) | 실제 공개 중인 모바일 웹 흐름 | +| Official download mirror | [download-vstalk.phy.kr](https://download-vstalk.phy.kr) | 공식 다운로드 미러 경로 | +| Public stage repo | [physia.kr/open-source/projects/public/kotalk](https://physia.kr/open-source/projects/public/kotalk) | 제2 공개 레포 | +| Forge releases | [git.physia.kr/ian/vs-messanger/releases](https://git.physia.kr/ian/vs-messanger/releases) | 내부 기준 릴리즈 채널 | +| GitHub releases | [github.com/werther24601/kotalk/releases](https://github.com/werther24601/kotalk/releases) | 공개 릴리즈 채널 | + +## Desktop Walkthrough + +데스크톱은 화려한 장식보다 `레일 + 목록 + 대화`에 집중합니다. 넓은 화면을 “정보를 더 많이 보여 주는 곳”이 아니라 “답장과 전환을 덜 피곤하게 만드는 곳”으로 해석하는 쪽에 가깝습니다. + +### What To Notice + +- 좌측 레일은 목적지 전환을 맡고, 설명 텍스트는 최소화합니다. +- 가운데 목록은 최근성, 읽지 않음, 고정 흐름을 조밀하게 처리합니다. +- 오른쪽 대화 패널은 입력과 복귀 흐름이 끊기지 않게 밀도를 높입니다. +- 멀티 윈도우 전제 설계라서 대화를 분리해 보는 흐름을 계속 강화하고 있습니다. + +### Desktop Screens + +| Screen | Why it matters | +|---|---| +| [hero-shell.png](docs/assets/latest/hero-shell.png) | 현재 데스크톱 전체 셸의 구조와 밀도 | +| [onboarding.png](docs/assets/latest/onboarding.png) | 첫 진입에서 어떤 정보를 먼저 보여주는지 | +| [conversation.png](docs/assets/latest/conversation.png) | 실제 대화 화면의 읽기 흐름과 입력 밀도 | + +## Mobile Web Walkthrough + +모바일 웹은 빠른 진입과 짧은 복귀가 핵심입니다. 설명을 길게 읽게 하기보다, 대화 목록과 검색, 보관, 다시 열기 흐름을 짧은 탭 구조로 분리합니다. + +### What To Notice + +- 온보딩은 길고 복잡한 가입보다 빠른 진입을 우선합니다. +- 목록은 최근 대화와 필터, 검색 진입을 한 화면에서 해결합니다. +- 검색은 단순 텍스트 필터가 아니라 다시 찾아야 하는 대화를 더 빨리 여는 방향으로 확장 중입니다. +- 보관 화면은 “나중에 다시 답장해야 할 것”을 모아 보는 허브 역할을 맡습니다. + +### Mobile Web Screens + +| Screen | Why it matters | +|---|---| +| [vstalk-web-onboarding.png](docs/assets/latest/vstalk-web-onboarding.png) | 초기 진입과 가입 흐름 | +| [vstalk-web-list.png](docs/assets/latest/vstalk-web-list.png) | 현재 받은함 구조 | +| [vstalk-web-search.png](docs/assets/latest/vstalk-web-search.png) | 검색과 재발견 흐름 | +| [vstalk-web-saved.png](docs/assets/latest/vstalk-web-saved.png) | 보관과 후속조치 허브 | +| [vstalk-web-chat.png](docs/assets/latest/vstalk-web-chat.png) | 모바일 대화 화면의 현재 밀도 | + +## Build And Artifact Shelf + +저장소 안에는 화면만 있는 것이 아니라, 현재 기준의 빌드 산출물과 최신 스크린샷도 함께 남깁니다. + +- 릴리즈 정책: [RELEASING.md](RELEASING.md) +- 현재 상태 요약: [PROJECT_STATUS.md](PROJECT_STATUS.md) +- 최신 화면 자산: [docs/assets/latest/README.md](docs/assets/latest/README.md) +- 제품 방향 전체: [문서/README.md](문서/README.md) + +## What This Showcase Is Trying To Prove + +이 문서는 “멋있어 보이는 이미지 모음”보다 아래 세 가지를 보여주려 합니다. + +- KoTalk가 단순한 기획 문서 저장소가 아니라 실제 화면과 빌드 결과를 가진 프로젝트라는 점 +- 한국어 데스크톱 메시징 경험을 다시 다듬는다는 방향이 UI와 정보 구조에 실제 반영되고 있다는 점 +- 현재 부족한 부분도 숨기지 않고, 상태 문서와 다음 작업 우선순위가 함께 공개돼 있다는 점 + +## Read Next + +- 현재 상태: [PROJECT_STATUS.md](PROJECT_STATUS.md) +- 배경과 문제의식: [BACKGROUND.md](BACKGROUND.md) +- 공개 규칙: [RELEASING.md](RELEASING.md) +- 제품 기획: [문서/README.md](문서/README.md) diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..42dbe94 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,33 @@ +# Support + +## Contact Channels + +- 일반 도움: [help@physia.kr](mailto:help@physia.kr) +- 제휴 및 운영 문의: [contact@physia.kr](mailto:contact@physia.kr) +- 보안 이슈: [ian@physia.kr](mailto:ian@physia.kr) + +## Best Way To Report A Problem + +가능하면 공개 이슈로 남겨 주세요. 아래 정보가 있으면 처리 속도가 높아집니다. + +- 운영체제와 버전 +- 실행 방법 +- 재현 절차 +- 기대 결과와 실제 결과 +- 스크린샷이나 로그 + +## Priority Areas + +- 메시지 전송 실패 또는 중복 전송 +- 세션 복구 문제 +- 공개 릴리즈 파일 무결성 이슈 +- [vstalk.phy.kr](https://vstalk.phy.kr) 또는 다운로드 경로 장애 + +## Before You Send A Message + +먼저 아래 문서를 보면 빠르게 답을 찾을 수 있습니다. + +- [FAQ.md](FAQ.md) +- [PROJECT_STATUS.md](PROJECT_STATUS.md) +- [RELEASING.md](RELEASING.md) +- [COMMUNITY.md](COMMUNITY.md) diff --git a/TRADEMARKS.md b/TRADEMARKS.md new file mode 100644 index 0000000..f6f56d3 --- /dev/null +++ b/TRADEMARKS.md @@ -0,0 +1,26 @@ +# Trademarks + +코드 라이선스와 상표 사용 권한은 다릅니다. + +## Marks + +아래 명칭과 관련 로고는 PHYSIA의 브랜드 자산으로 취급합니다. + +- `KoTalk` +- `코톡` +- `kotalk` +- `ko-talk` +- `PHYSIA` + +## What The Open-Source License Does Not Grant + +- 공식 서비스로 오인되는 브랜딩 +- 동일하거나 혼동 가능한 제품명 사용 +- PHYSIA의 공식 후원이나 인증을 암시하는 표시 + +## Forks And Self-Hosted Deployments + +코드 포크와 자체 호스팅은 라이선스 범위 안에서 가능합니다. +다만 공식 서비스와 혼동될 수 있는 브랜드 사용은 허용되지 않습니다. + +문의: [contact@physia.kr](mailto:contact@physia.kr) diff --git a/TRUST_CENTER.md b/TRUST_CENTER.md new file mode 100644 index 0000000..397d264 --- /dev/null +++ b/TRUST_CENTER.md @@ -0,0 +1,27 @@ +# Trust Center + +KoTalk의 신뢰 표면은 과장된 보안 문구보다 현재 통제와 남은 과제를 함께 적는 방식으로 관리합니다. + +## Current Trust Baseline + +| Area | Current read | +|---|---| +| Authentication | 기본 토큰/세션 구조와 만료·회전 정책을 사용 | +| Realtime | 실시간 연결은 별도 보호 경계를 둠 | +| Storage | 민감값 장기 저장 최소화 방향 | +| Release integrity | 릴리즈 경로와 스크린샷, CHANGELOG를 함께 맞춤 | +| Public posture | 적용 범위와 미완료 범위를 분리해 설명 | + +## What Is Still In Progress + +- 브라우저 세션 경계 강화 +- advisory 공개 체계 +- 키 회전과 백업/복구 증빙 +- 규제 환경 설치 검증 + +## Related Docs + +- [SECURITY.md](SECURITY.md) +- [SECURITY_RESPONSE.md](SECURITY_RESPONSE.md) +- [PRIVACY_AND_DATA_HANDLING.md](PRIVACY_AND_DATA_HANDLING.md) +- [DEPLOYMENT_MODES.md](DEPLOYMENT_MODES.md) diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..e00a965 --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,11 @@ +COMPOSE_PROJECT_NAME=kotalk +ACME_EMAIL=admin@example.com +API_HOST=api.example.com +DOWNLOAD_HOST=download-vstalk.phy.kr +DOWNLOAD_ROOT=/srv/kotalk/download +WEBAPP_HOST=vstalk.phy.kr +WEBAPP_RELEASE_ROOT=/srv/kotalk/webapp/current +BOOTSTRAP_INVITE_CODE=change-me-in-private-env +JWT_ISSUER=KoTalk +JWT_AUDIENCE=KoTalk.Client +JWT_SIGNING_KEY=change-me-to-a-long-random-value diff --git a/deploy/Caddyfile b/deploy/Caddyfile new file mode 100644 index 0000000..55d4b92 --- /dev/null +++ b/deploy/Caddyfile @@ -0,0 +1,49 @@ +{ + email {$ACME_EMAIL} +} + +{$DOWNLOAD_HOST} { + encode zstd gzip + root * /srv/download + redir /windows /windows/latest 302 + redir /android /android/latest 302 + redir /windows/latest /windows/latest/VsMessenger-win-x64.zip 302 + redir /android/latest /android/latest/VsMessenger-android-universal.apk 302 + redir /latest /latest/version.json 302 + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + } + file_server +} + +{$WEBAPP_HOST:vstalk.phy.kr} { + encode zstd gzip + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + } + handle /v1/* { + reverse_proxy api:8080 + } + handle /health { + reverse_proxy api:8080 + } + handle { + reverse_proxy webapp:80 + } +} + +{$API_HOST} { + encode zstd gzip + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + } + reverse_proxy api:8080 +} diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..30a65d3 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,29 @@ +# Deployment Guide + +이 디렉터리는 KoTalk의 범용 배포 골격을 담습니다. + +## Public Endpoints + +- 모바일 웹: [vstalk.phy.kr](https://vstalk.phy.kr) +- 공식 다운로드 미러: [download-vstalk.phy.kr](https://download-vstalk.phy.kr) +- 버전 메타데이터: [download-vstalk.phy.kr/latest/version.json](https://download-vstalk.phy.kr/latest/version.json) + +## Intended Shape + +- `Caddyfile`: 웹 진입점, 다운로드 미러, API 프록시 +- `compose*.yml`: API, 정적 웹, 보조 서비스 구성 +- `docker/`: 이미지 빌드 정의 + +## Public Rules + +- 실서비스 호스트 주소, 관리자 계정, 비밀값은 공개 문서에 적지 않습니다. +- 운영 중인 컨테이너명과 네트워크명은 공개 표면의 필수 정보가 아닙니다. +- 배포 예시는 범용 구조 중심으로 유지합니다. + +## Download Layout + +- `/windows/latest` +- `/android/latest` +- `/latest/version.json` + +실제 공개 릴리즈 경로는 [RELEASING.md](../RELEASING.md)와 함께 봐야 합니다. diff --git a/deploy/compose.mvp.yml b/deploy/compose.mvp.yml new file mode 100644 index 0000000..fedd324 --- /dev/null +++ b/deploy/compose.mvp.yml @@ -0,0 +1,42 @@ +services: + caddy: + image: caddy:2.9-alpine + restart: unless-stopped + depends_on: + - api + ports: + - "80:80" + - "443:443" + environment: + ACME_EMAIL: ${ACME_EMAIL} + API_HOST: ${API_HOST} + DOWNLOAD_HOST: ${DOWNLOAD_HOST} + WEBAPP_HOST: ${WEBAPP_HOST:-vstalk.phy.kr} + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + - ${DOWNLOAD_ROOT}:/srv/download:ro + + api: + build: + context: .. + dockerfile: deploy/docker/api.Dockerfile + restart: unless-stopped + environment: + ASPNETCORE_ENVIRONMENT: Production + ASPNETCORE_URLS: http://0.0.0.0:8080 + ConnectionStrings__Main: Data Source=/data/vs-messenger.db + Auth__Jwt__Issuer: ${JWT_ISSUER} + Auth__Jwt__Audience: ${JWT_AUDIENCE} + Auth__Jwt__SigningKey: ${JWT_SIGNING_KEY} + Bootstrap__InviteCodes__0: ${BOOTSTRAP_INVITE_CODE} + volumes: + - api-data:/data + expose: + - "8080" + +volumes: + caddy-config: + caddy-data: + api-data: diff --git a/deploy/compose.webapp.yml b/deploy/compose.webapp.yml new file mode 100644 index 0000000..f9cbd98 --- /dev/null +++ b/deploy/compose.webapp.yml @@ -0,0 +1,9 @@ +services: + webapp: + image: nginx:1.27-alpine + restart: unless-stopped + volumes: + - ${WEBAPP_RELEASE_ROOT:-/srv/vs-messanger/webapp/current}:/usr/share/nginx/html:ro + - ./docker/webapp.nginx.conf:/etc/nginx/conf.d/default.conf:ro + expose: + - "80" diff --git a/deploy/docker/api.Dockerfile b/deploy/docker/api.Dockerfile new file mode 100644 index 0000000..e7111b9 --- /dev/null +++ b/deploy/docker/api.Dockerfile @@ -0,0 +1,25 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ["PhysOn.sln", "./"] +COPY ["global.json", "./"] +COPY ["src/PhysOn.Api/PhysOn.Api.csproj", "src/PhysOn.Api/"] +COPY ["src/PhysOn.Application/PhysOn.Application.csproj", "src/PhysOn.Application/"] +COPY ["src/PhysOn.Contracts/PhysOn.Contracts.csproj", "src/PhysOn.Contracts/"] +COPY ["src/PhysOn.Domain/PhysOn.Domain.csproj", "src/PhysOn.Domain/"] +COPY ["src/PhysOn.Infrastructure/PhysOn.Infrastructure.csproj", "src/PhysOn.Infrastructure/"] + +RUN dotnet restore "src/PhysOn.Api/PhysOn.Api.csproj" + +COPY . . +RUN dotnet publish "src/PhysOn.Api/PhysOn.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime +WORKDIR /app + +ENV ASPNETCORE_URLS=http://0.0.0.0:8080 +EXPOSE 8080 + +COPY --from=build /app/publish . + +ENTRYPOINT ["dotnet", "PhysOn.Api.dll"] diff --git a/deploy/docker/webapp.nginx.conf b/deploy/docker/webapp.nginx.conf new file mode 100644 index 0000000..90a6209 --- /dev/null +++ b/deploy/docker/webapp.nginx.conf @@ -0,0 +1,23 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /assets/ { + expires 7d; + add_header Cache-Control "public, max-age=604800, immutable"; + try_files $uri =404; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ { + expires 7d; + add_header Cache-Control "public, max-age=604800, immutable"; + try_files $uri =404; + } +} diff --git a/deploy/docker/worker.Dockerfile b/deploy/docker/worker.Dockerfile new file mode 100644 index 0000000..1b3ed09 --- /dev/null +++ b/deploy/docker/worker.Dockerfile @@ -0,0 +1,22 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /src + +COPY ["PhysOn.sln", "./"] +COPY ["global.json", "./"] +COPY ["src/PhysOn.Worker/PhysOn.Worker.csproj", "src/PhysOn.Worker/"] +COPY ["src/PhysOn.Application/PhysOn.Application.csproj", "src/PhysOn.Application/"] +COPY ["src/PhysOn.Contracts/PhysOn.Contracts.csproj", "src/PhysOn.Contracts/"] +COPY ["src/PhysOn.Domain/PhysOn.Domain.csproj", "src/PhysOn.Domain/"] +COPY ["src/PhysOn.Infrastructure/PhysOn.Infrastructure.csproj", "src/PhysOn.Infrastructure/"] + +RUN dotnet restore "src/PhysOn.Worker/PhysOn.Worker.csproj" + +COPY . . +RUN dotnet publish "src/PhysOn.Worker/PhysOn.Worker.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime +WORKDIR /app + +COPY --from=build /app/publish . + +ENTRYPOINT ["dotnet", "PhysOn.Worker.dll"] diff --git a/deploy/systemd/vs-messanger-mvp.service b/deploy/systemd/vs-messanger-mvp.service new file mode 100644 index 0000000..db20a70 --- /dev/null +++ b/deploy/systemd/vs-messanger-mvp.service @@ -0,0 +1,16 @@ +[Unit] +Description=vs-messanger MVP docker compose stack +Requires=docker.service +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +WorkingDirectory=/srv/vs-messanger/app +RemainAfterExit=yes +ExecStart=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml up -d --build --remove-orphans +ExecStop=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml down +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/vs-messanger-webapp.service b/deploy/systemd/vs-messanger-webapp.service new file mode 100644 index 0000000..a564f4e --- /dev/null +++ b/deploy/systemd/vs-messanger-webapp.service @@ -0,0 +1,16 @@ +[Unit] +Description=vs-messanger mobile webapp compose stack +Requires=docker.service +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +WorkingDirectory=/srv/vs-messanger/app +RemainAfterExit=yes +ExecStart=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml -f deploy/compose.webapp.yml up -d webapp caddy +ExecStop=/usr/bin/docker compose --project-name vs-messanger --env-file deploy/.env -f deploy/compose.mvp.yml -f deploy/compose.webapp.yml stop webapp +TimeoutStartSec=0 + +[Install] +WantedBy=multi-user.target diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..15130e7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +# Docs Index + +`docs/`는 공개 저장소 표면을 보조하는 문서 루트입니다. 제품 마스터 플랜 자체는 `문서/`를 기준으로 유지하고, `docs/`는 공개 자산, 저장소 운영 규칙, 보관용 초안을 맡습니다. + +## Included Areas + +- [assets/](assets/): README, Showcase, 최신 스크린샷, 공개 시각 자산 +- [archive/](archive/): 초기 기획 초안과 구조 탐색 문서 +- [repository-surfaces.md](repository-surfaces.md): 내부 기준선, 공개 큐레이션 브랜치, 비공개 워크스페이스 규칙 + +현재 기준의 최종 기획 본문과 UX 아틀라스는 [../문서/README.md](../문서/README.md)를 우선합니다. diff --git a/docs/archive/01-product-strategy-definition.md b/docs/archive/01-product-strategy-definition.md new file mode 100644 index 0000000..76e2735 --- /dev/null +++ b/docs/archive/01-product-strategy-definition.md @@ -0,0 +1,217 @@ +# 01. 제품 전략 및 정의 + +## 합의 관점 + +이 문서는 아래 6개 전문 관점의 공통 합의안으로 정리한다. + +- 제품 전략: 출시 가능성과 포지셔닝을 우선한다. +- UX 설계: 익숙함은 유지하되 더 정돈된 정보 밀도와 반응성을 만든다. +- Windows 데스크톱: 키보드 중심 흐름과 멀티패널 효율을 핵심으로 본다. +- 채팅 플랫폼: 실시간성보다 "안정적 전달, 빠른 재진입, 파일 전송 신뢰성"을 우선한다. +- 브랜드/법무: 카카오톡과 혼동될 수 있는 요소는 배제한다. +- 수익/성장: 소규모 개인 프로젝트로도 운영 가능한 구조를 택한다. + +## 제품 비전 + +- 목표는 "Windows에서 가장 빠르고 정돈된 개인용 메신저"를 만드는 것이다. +- 출발점은 카카오톡 PC의 익숙한 사용 패턴이지만, 최종 제품은 더 세련된 데스크톱 경험과 개인 생산성 보조 기능을 갖춘 독립 서비스여야 한다. +- 핵심 가치 제안: +- 빠른 대화 접근: 실행, 로그인, 방 전환, 검색이 가볍고 즉각적이어야 한다. +- 높은 작업 효율: 키보드 단축키, 멀티패널, 파일/링크/이미지 관리가 우수해야 한다. +- 개인 사이드 프로젝트다운 완성도: 과한 범용 플랫폼이 아니라 "한 명이 끝까지 유지 가능한 범위"에서 품질을 높인다. + +## 타깃 사용자 + +- 1차 타깃: Windows PC를 오래 켜두고 일하거나 공부하는 20~40대 개인 사용자 +- 1차 타깃 특성: +- 모바일보다 PC에서 대화와 파일 공유를 더 많이 처리한다. +- 오픈채팅/커뮤니티보다 소수 관계, 개인 네트워크, 협업성 대화에 가치를 둔다. +- 메신저 자체보다 "업무/일상 컨텍스트 전환 비용"을 줄이고 싶어 한다. +- 2차 타깃: 개인 프로젝트 팀, 지인 소모임, 스터디 그룹, 소규모 프리랜서 협업 그룹 + +## 핵심 JTBD + +- "PC에서 일하는 동안, 휴대폰을 들지 않고도 대화를 빠르게 처리하고 싶다." +- "자주 대화하는 사람, 중요한 파일, 링크, 공지를 메신저 안에서 쉽게 다시 찾고 싶다." +- "메신저가 가볍고 안정적이어서 계속 켜 두어도 부담이 없었으면 좋겠다." +- "읽음 상태, 미확인 메시지, 첨부파일, 핀 고정 정보를 한눈에 관리하고 싶다." +- "사적인 메신저라도 디자인이 투박하지 않고, 데스크톱 앱다운 완성도가 있었으면 좋겠다." + +## MVP 범위 + +MVP는 "사람들이 실제로 매일 켜 둘 수 있는 1:1 및 소규모 그룹 메신저"에 집중한다. + +- 필수 계정 기능: +- 이메일 또는 휴대폰 기반 가입/로그인 +- Windows 디바이스 기준 세션 유지 +- 프로필 이미지, 상태 메시지, 표시 이름 +- 필수 대화 기능: +- 1:1 채팅 +- 그룹 채팅 +- 텍스트, 이모지, 이미지, 파일 전송 +- 읽음 상태, 전송 실패 재시도, 날짜 구분선, 미확인 배지 +- 대화방 고정, 알림 음소거, 검색 +- 필수 데스크톱 UX: +- 좌측 채팅 리스트 + 중앙 대화 패널 + 선택형 우측 정보 패널 +- 글로벌 검색 +- 드래그 앤 드롭 파일 전송 +- 단축키 중심 이동 +- 시스템 트레이 상주, 알림센터 연동 +- 필수 운영 기능: +- 신고/차단 +- 관리자용 기본 운영 콘솔 +- 장애/로그 모니터링 + +## MVP에서 제외할 것 + +- 음성/영상 통화 +- 공개 커뮤니티, 대형 오픈채팅 +- 스토리, 피드, 쇼츠형 콘텐츠 +- 결제, 송금, 선물하기 +- AI 비서 전면 탑재 +- 과한 커스터마이징 테마 마켓 +- 멀티플랫폼 동시 최적화 + +제외 이유: +- 개인 사이드 프로젝트가 첫 출시까지 가는 데 가장 큰 장애물은 범위 과잉이다. +- 통화/결제/콘텐츠 기능은 법률, 인프라, 운영 부담이 급증한다. + +## 기능 우선순위 + +### P0 + +- 가입/로그인 +- 친구 또는 사용자 검색 +- 1:1 채팅 +- 그룹 채팅 +- 메시지 영속 저장 +- 이미지/파일 전송 +- 읽음 상태 +- 푸시/데스크톱 알림 +- 채팅 리스트 정렬과 미확인 배지 +- 대화 검색 + +### P1 + +- 답장, 전달, 메시지 고정 +- 링크/파일/미디어 모아보기 +- 멀티 디바이스 로그인 관리 +- 차단/신고 +- 관리 도구와 운영 로그 + +### P2 + +- 예약 전송 +- 나에게 보내기 +- 읽지 않은 대화 일괄 정리 +- 생산성형 미니 기능: 체크리스트, 빠른 메모, 링크 컬렉션 + +## 단계별 출시 플랜 + +### Phase 0. 정의 및 검증 + +- 기준 제품 경험을 분해한다. +- "왜 카카오톡 PC가 편한가"를 기능 복제가 아니라 흐름 단위로 정리한다. +- 5~10명의 잠재 사용자 인터뷰로 Windows 메신저 사용 패턴을 검증한다. +- 브랜드 방향, 시각 언어, 법적 가드레일을 먼저 확정한다. + +### Phase 1. Private Alpha + +- 본인 + 지인 소수 그룹이 실제로 쓰는 수준까지 구현한다. +- 안정성과 기본 채팅 품질에만 집중한다. +- 서버는 본 VPS에 단일 리전으로 구축하되, 백업과 로그는 반드시 분리한다. +- 성공 기준은 "매일 열어두는가"와 "메시지 손실이 없는가"다. + +### Phase 2. Closed Beta + +- 50~200명 수준으로 확장한다. +- 검색, 파일, 알림, 운영 도구를 강화한다. +- 설치/업데이트 경험과 계정 복구 흐름을 다듬는다. +- 이 시점부터 지표 기반 개선 루프를 시작한다. + +### Phase 3. Public Launch + +- 브랜딩과 온보딩을 정식화한다. +- 유료 옵션이 있다면 이 단계에서만 소프트 런칭한다. +- Windows 앱 완성도를 우선 홍보 포인트로 삼는다. + +## 차별화 방향 + +- "카카오톡 대체재"보다 "Windows에서 더 잘 맞는 메신저"로 포지셔닝한다. +- 차별화 포인트: +- 정돈된 데스크톱 레이아웃과 높은 정보 밀도 +- 키보드 우선 UX와 빠른 탐색 +- 링크/파일/이미지 재발견 경험 강화 +- 가볍고 신뢰 가능한 앱 성능 +- 개인 사용자와 소규모 그룹에 맞는 프라이빗한 톤 + +- 피해야 할 차별화: +- 기능 수를 늘려 거대 플랫폼처럼 보이게 하는 것 +- AI, 커뮤니티, 콘텐츠 허브를 한 번에 붙이는 것 + +## 수익화 옵션 + +- 기본 원칙: +- 초기에는 무료가 맞다. +- 수익화는 제품이 일상 사용 습관에 들어온 뒤 붙여야 한다. + +- 가능한 옵션: +- Pro 개인 요금제 +- 대용량 파일 업로드 +- 긴 메시지/미디어 보관 기간 +- 향상된 검색과 기록 내보내기 +- 테마/레이아웃 커스터마이징 +- 소규모 팀 요금제 +- 관리자 기능 +- 공유 드라이브형 파일 관리 +- 간단한 공지/권한 관리 + +- 추천 방향: +- 첫 유료 기능은 광고가 아니라 "보관/검색/파일 한도" 기반이 가장 현실적이다. + +## 브랜드 및 법적 주의사항 + +- 카카오톡의 이름, 색상 조합, 아이콘 메타포, 말풍선 스타일, 사운드, 카피 문구를 그대로 차용하지 않는다. +- "카카오톡 PC 클론"처럼 보이는 표현은 내부 참고용으로만 쓰고 외부 문서나 마케팅에는 쓰지 않는다. +- 프로토콜 역공학, 기존 네트워크에 붙는 브리지, 상표 혼동 가능성이 있는 UX 복제는 피한다. +- 참고 대상은 "사용 흐름"과 "데스크톱 생산성 원칙"이어야 하며, 결과물은 별도 브랜드 아이덴티티를 가져야 한다. +- 출시 전 최소 점검 항목: +- 서비스명 상표 검색 +- 앱 아이콘과 UI 주요 요소의 혼동 가능성 리뷰 +- 이용약관/개인정보처리방침 초안 + +## 성공 지표 + +### 제품 핵심 지표 + +- 주간 활성 사용자 비율 +- 7일, 30일 리텐션 +- DAU/WAU +- 1인당 일평균 세션 수 +- 1인당 일평균 메시지 송수신량 + +### 경험 품질 지표 + +- 앱 실행 후 첫 화면 표시 시간 +- 메시지 전송 성공률 +- 이미지/파일 업로드 성공률 +- 알림 수신 성공률 +- 검색 성공률 또는 검색 후 클릭률 + +### 사업성 보조 지표 + +- 초대 전환율 +- 그룹 생성 비율 +- 1명 이상의 재초대 발생 비율 +- 유료 기능 대기자 등록률 + +## 최종 권고 + +- 이 프로젝트는 "카카오톡과 비슷한 것"을 목표로 두면 늦어진다. +- 더 좋은 방향은 "Windows에서 쓰기 가장 좋은 개인 메신저"를 만드는 것이다. +- 첫 출시의 기준: +- 매일 켜 두고 쓸 만큼 빠를 것 +- 메시지와 파일이 불안하지 않을 것 +- 기존 대형 메신저보다 데스크톱 경험이 더 정돈되어 있을 것 + +이 기준을 지키면 개인 사이드 프로젝트로도 실제 출시 가능한 수준까지 갈 수 있다. diff --git a/docs/archive/README.md b/docs/archive/README.md new file mode 100644 index 0000000..b12363b --- /dev/null +++ b/docs/archive/README.md @@ -0,0 +1,9 @@ +# Archive Docs + +이 디렉터리는 초기에 작성된 기획 초안과 구조 탐색 문서를 보관합니다. + +원칙: + +- 현재 기준의 최종 기획 본문은 `문서/`를 우선합니다. +- 여기 있는 문서는 참고용 아카이브이며, 현재 제품 기준과 다를 수 있습니다. +- 공개 브랜치에서는 이 경로를 포함하지 않거나 축약할 수 있습니다. diff --git a/docs/archive/planning/01-backend-platform-architecture.md b/docs/archive/planning/01-backend-platform-architecture.md new file mode 100644 index 0000000..cc965b8 --- /dev/null +++ b/docs/archive/planning/01-backend-platform-architecture.md @@ -0,0 +1,515 @@ +# 개인 메신저 프로젝트 백엔드/플랫폼 아키텍처 합의안 + +이 문서는 기존 Rocky Linux VPS 위에 Docker로 채팅 서버를 구축하는 것을 전제로 한 구현 지향형 백엔드/플랫폼 설계안이다. 목표는 카카오톡 PC 버전 수준의 빠른 체감 응답성과 안정적인 실시간 동기화 경험을 내는 것이다. + +합의에 참여한 전문 관점: + +- 리얼타임 백엔드 아키텍트 +- 플랫폼 엔지니어 +- PostgreSQL 데이터 엔지니어 +- SRE/관측성 엔지니어 +- 보안 엔지니어 +- 스토리지/백업 엔지니어 + +## 1. 전제와 설계 원칙 + +전제: + +- 서버는 기존 Rocky Linux VPS에 배포한다. +- Docker 사용 가능하다. +- 초기 개발 대상은 Windows PC 클라이언트다. +- 초기는 개인 사이드 프로젝트지만, 구조는 다중 디바이스와 사용자 증가를 막지 않아야 한다. + +설계 원칙: + +- 초기에는 단일 VPS에 맞는 단순성을 우선한다. +- 그러나 프로토콜, 데이터 모델, 메시지 ID, 스토리지 인터페이스는 이후 수평 확장을 막지 않게 설계한다. +- 채팅 메시지의 원본 저장소는 처음부터 PostgreSQL로 통일한다. +- 실시간 연결과 영속 저장을 분리해 생각한다. +- MinIO는 첨부파일 저장소로는 괜찮지만 백업 저장소로 간주하지 않는다. +- 운영 편의보다 복구 가능성을 우선한다. + +## 2. 최종 권장 아키텍처 한 줄 요약 + +초기 최적안은 `단일 애플리케이션 계열 + PostgreSQL + Redis + MinIO + Caddy` 조합이다. + +- 클라이언트 외부 프로토콜은 `HTTPS REST + WSS(WebSocket over TLS)`를 사용한다. +- 서버 애플리케이션은 논리적으로 `API/Realtime/Worker`로 분리하되, 초기에는 같은 코드베이스와 같은 이미지에서 역할만 나눠 배포한다. +- 영속 데이터는 PostgreSQL 하나를 기준축으로 삼는다. +- Redis는 presence, 세션 인덱스, 단기 캐시, fan-out 보조 용도로만 사용하고, 진실의 원천으로 쓰지 않는다. +- 첨부파일과 프로필 이미지는 MinIO에 저장하되 S3 호환 계층으로 추상화해서 이후 외부 오브젝트 스토리지로 쉽게 옮길 수 있게 한다. + +## 3. 왜 이 아키텍처가 현재 VPS에 가장 적합한가 + +이 VPS는 단일 머신이다. 따라서 초기에 Kafka, 다수의 마이크로서비스, 복잡한 서비스 메시를 올리면 운영 부담이 가치보다 커진다. 반대로 단일 앱 컨테이너 하나에 모든 역할을 몰아넣고 Redis 없이 메모리만 사용하면, 나중에 멀티 인스턴스나 재시작 복구, presence 일관성, fan-out 제어에서 다시 뜯어고쳐야 한다. + +따라서 가장 균형이 좋은 선택은 아래와 같다. + +- 앱은 단순하게 유지한다. +- 상태 저장은 PostgreSQL로 고정한다. +- 연결 상태와 단기 이벤트 전달은 Redis에 맡긴다. +- 첨부파일은 애플리케이션 로컬 디스크가 아니라 S3 호환 저장소로 보낸다. +- 프록시는 Caddy로 단순화한다. + +이렇게 하면 MVP에서는 운영 난이도가 낮고, 이후 앱 복제본을 늘릴 때도 방향이 유지된다. + +## 4. 외부 프로토콜 권장안 + +### 4.1 클라이언트 통신 + +권장: + +- 인증, 초기 동기화, 대화방 목록, 과거 메시지 조회, 첨부 업로드 시작/완료: `HTTPS REST` +- 실시간 메시지 수신, 읽음 상태, 타이핑 상태, presence, 서버 이벤트 푸시: `WSS` + +선택 이유: + +- Windows 데스크톱에서 구현과 디버깅이 쉽다. +- 프록시/Caddy/TLS 환경에서 안정적이다. +- 모바일/웹 클라이언트로 확장해도 동일 패턴을 유지하기 쉽다. +- gRPC streaming보다 도입 장벽이 낮고, 브라우저/데스크톱 지원이 직관적이다. + +권장하지 않는 초기 선택: + +- 순수 TCP 커스텀 프로토콜: 성능 이점보다 운영/보안/프록시 복잡성이 커진다. +- MQTT: 메신저 도메인 모델과 인증/권한/히스토리 조회가 결국 별도 API를 필요로 하므로 단순해지지 않는다. +- WebRTC data channel 중심 설계: P2P는 NAT, 오프라인 저장, 멀티 디바이스 동기화에 불리하다. + +### 4.2 앱 레벨 메시지 프레임 + +초기부터 다음 개념은 반드시 포함한다. + +- `message_id`: ULID 또는 UUIDv7 +- `conversation_id` +- `sender_user_id` +- `client_request_id`: 중복 전송 방지용 +- `server_sequence`: 대화방 단위 또는 사용자 inbox 단위 순서값 +- `sent_at`, `delivered_at`, `read_at` +- `event_version` +- `device_id` + +이 필드가 있으면 이후 재전송, 읽음 동기화, 멀티 디바이스, 중복 ACK 처리, 서버 재시작 복구가 쉬워진다. + +## 5. 서비스 구성 권장안 + +초기 컨테이너 구성: + +- `messenger-caddy` + 외부 80/443 수신, TLS 종료, WebSocket 업그레이드 처리, 정적 헬스 라우팅 +- `messenger-app` + REST API와 WebSocket 게이트웨이 역할 수행 +- `messenger-worker` + outbox 처리, 알림 발행, 썸네일/미디어 후처리, 정리 배치 작업 +- `messenger-postgres` + 핵심 영속 데이터 저장소 +- `messenger-redis` + presence, session map, short-lived cache, publish/subscribe 보조 +- `messenger-minio` + 첨부파일, 프로필 이미지, 미디어 저장 +- `messenger-backup` + PostgreSQL 백업과 MinIO 메타/버킷 동기화 작업 +- `messenger-prometheus` + 메트릭 수집 +- `messenger-loki` + 로그 저장 +- `messenger-grafana` + 대시보드와 알림 뷰 +- `messenger-node-exporter` 또는 유사 호스트 메트릭 수집기 + VPS CPU, 메모리, 디스크, 네트워크 관측 + +중요한 점: + +- `messenger-app`과 `messenger-worker`는 같은 코드베이스를 쓰는 것이 좋다. +- 초기에는 마이크로서비스로 쪼개지 않는다. +- 역할만 분리해 컨테이너를 나누면, 장애면을 분리하고 추후 스케일 아웃하기 쉬워진다. + +## 6. 데이터 저장 전략 + +### 6.1 PostgreSQL을 중심으로 두는 이유 + +채팅 서비스는 결국 다음을 안정적으로 다뤄야 한다. + +- 대화방 생성/멤버십 +- 메시지 영속화 +- 읽음 상태 +- 첨부 메타데이터 +- 차단/숨김/핀/알림 설정 +- 운영 감사 로그 + +이 영역은 관계형 정합성이 중요하다. PostgreSQL은 트랜잭션, 인덱스, JSONB, 전문 검색, WAL 기반 백업, 추후 복제까지 한 번에 가져갈 수 있다. + +초기에는 NoSQL보다 PostgreSQL이 훨씬 안전하다. + +### 6.2 핵심 테이블 범주 + +초기부터 분리해서 설계할 범주: + +- 사용자/프로필 +- 디바이스/세션 +- 친구 관계 또는 연락처 매핑 +- 대화방 +- 대화방 멤버 +- 메시지 +- 메시지 첨부 메타 +- 메시지 전달 상태 +- 읽음 커서 +- 리액션 +- 차단/뮤트/보관 +- 감사 로그 +- 아웃박스 이벤트 + +핵심 원칙: + +- 메시지 본문은 PostgreSQL에 저장한다. +- 첨부파일 바이너리는 MinIO에 저장하고 DB에는 메타와 경로만 둔다. +- 읽음 상태는 사용자별 커서 모델을 우선한다. 메시지별 읽음 row를 무분별하게 만들면 커진다. + +### 6.3 Redis 역할 제한 + +Redis는 아래만 맡기는 것이 맞다. + +- 온라인 사용자 presence TTL +- 웹소켓 세션 라우팅 인덱스 +- 단기 rate limit 카운터 +- 최근 조회 캐시 +- 멀티 인스턴스 fan-out 보조 pub/sub + +Redis를 진실의 원천으로 두면 복구와 디버깅이 어려워진다. 메시지 원본이나 유일한 unread 상태를 Redis에 넣는 것은 피한다. + +## 7. 실시간 메시징 처리 흐름 권장안 + +메시지 전송 처리 흐름: + +1. 클라이언트가 `client_request_id` 포함 메시지 전송 +2. 서버가 권한과 대화방 멤버십 확인 +3. PostgreSQL 트랜잭션에서 메시지 row 저장 +4. 같은 트랜잭션에서 outbox 이벤트 저장 +5. 커밋 성공 후 송신자에게 서버 확정 ACK 반환 +6. worker가 outbox를 읽고 수신자 online 세션으로 fan-out +7. 수신 클라이언트 ACK 수신 후 전달 상태 갱신 +8. 읽음 이벤트는 별도 커서 업데이트로 처리 + +이 구조의 장점: + +- DB 커밋 전송 실패와 푸시 실패를 분리할 수 있다. +- 서버가 내려갔다가 올라와도 outbox 기준으로 재처리 가능하다. +- 이후 Redis pub/sub에서 NATS JetStream 같은 것으로 바꿔도 모델이 유지된다. + +## 8. 첨부파일/미디어 저장 전략 + +초기 권장: + +- 원본 파일은 MinIO에 저장 +- 업로드는 서버에서 발급한 제한적 업로드 정책 또는 서버 경유 업로드 중 하나를 선택 +- MVP에서는 서버 경유 업로드가 구현은 쉽지만, 대용량에서 병목이 된다 +- 가능하면 초반부터 S3 호환 업로드 패턴으로 설계하는 것이 낫다 + +권장 메타데이터: + +- object_key +- original_filename +- mime_type +- byte_size +- image_width, image_height +- checksum +- upload_status +- virus_scan_status 또는 reserve 필드 + +확장 포인트: + +- MinIO를 나중에 Cloudflare R2, AWS S3, Backblaze B2로 옮기기 쉽게 추상화 +- 썸네일과 프리뷰는 worker에서 비동기 생성 + +## 9. 인증/세션 전략 + +Windows PC만 먼저 개발하더라도 처음부터 아래를 고려한다. + +- 사용자 계정과 디바이스 세션 분리 +- `device_id`를 가진 장치별 refresh session 유지 +- access token은 짧게, refresh token은 서버 추적 가능하게 +- 강제 로그아웃, 특정 디바이스 로그아웃, 세션 폐기 가능해야 함 + +권장: + +- 앱 로그인 후 디바이스 세션 생성 +- WebSocket 연결 시 access token 검증 +- 세션 폐기 시 WebSocket 강제 끊기 + +이렇게 해야 추후 모바일 앱을 붙여도 모델을 바꾸지 않는다. + +## 10. VPS 배포 전략 + +### 10.1 배포 방식 + +권장: + +- Docker Compose 기반 단일 프로젝트 스택 +- systemd에서 compose 스택을 부팅 시 자동 기동 +- 상태 저장 볼륨은 명확히 분리 + +권장 볼륨 범주: + +- PostgreSQL data +- Redis data +- MinIO data +- Grafana data +- Prometheus data +- Loki data +- 앱 업로드 임시 디렉터리 + +주의: + +- 기존 VPS에 다른 서비스가 있다면 네트워크와 도메인을 분리해야 한다. +- 외부 80/443을 이미 다른 Caddy가 쓰고 있다면, 새 메신저 스택은 기존 프록시에 역방향 프록시로 붙이는 방식이 가장 안전하다. +- 메신저 전용 서브도메인을 분리한다. 예: `msg.example.com`, `api.msg.example.com`, `ws.msg.example.com`, `media.msg.example.com` + +### 10.2 네트워크/포트 + +외부 공개는 최소화한다. + +- 외부 공개: `80`, `443`, `22` +- 내부 전용: PostgreSQL, Redis, MinIO 관리 포트, Grafana, Prometheus, Loki + +원칙: + +- DB/Redis/MinIO는 외부에 직접 열지 않는다. +- 관리자 UI가 필요하면 VPN 또는 SSH 터널 기반 접근만 허용한다. + +### 10.3 리소스 운영 방침 + +초기에는 다음 우선순위로 리소스를 배정한다. + +1. PostgreSQL 메모리 보장 +2. 앱/WebSocket 연결 처리 여유 확보 +3. MinIO 디스크 여유 확보 +4. 관측성 스택은 과하지 않게 운영 + +관측 스택이 너무 무거우면 다음 순서로 경량화한다. + +- Grafana 유지 +- Prometheus retention 축소 +- Loki retention 축소 +- 고해상도 scrape interval 완화 + +## 11. 관측성 설계 + +최소한 아래는 반드시 수집한다. + +애플리케이션 메트릭: + +- 활성 WebSocket 연결 수 +- 인증 성공/실패 수 +- 메시지 송신 TPS +- 메시지 저장 지연 +- outbox 적체 수 +- fan-out 지연 +- 읽음 이벤트 처리량 +- 업로드 성공/실패 수 +- 대화방별 초당 메시지 폭주 탐지 + +인프라 메트릭: + +- CPU +- 메모리 +- 디스크 사용량 +- 디스크 IOPS +- 네트워크 in/out +- 컨테이너 재시작 횟수 +- PostgreSQL connections, replication lag 향후 대비 +- Redis memory eviction 여부 + +로그: + +- JSON 구조화 로그 +- request_id, user_id, device_id, conversation_id, message_id 등 상관 키 포함 +- 인증 실패, 권한 오류, 메시지 저장 실패는 별도 레벨 관리 + +대시보드 우선순위: + +1. 서비스 헬스 +2. 메시지 송수신 지연 +3. DB 상태 +4. 첨부 업로드 상태 +5. 에러 로그 상위 유형 + +## 12. 백업 및 복구 전략 + +가장 중요한 원칙: + +- 같은 VPS 안의 다른 디렉터리에 저장한 사본은 백업이 아니다. +- MinIO에 저장한 DB 덤프도 MinIO가 같은 머신에 있으면 재해 복구가 아니다. + +권장 백업 구조: + +- PostgreSQL: 일간 전체 백업 + 지속적 WAL 아카이빙 +- 백업 저장 위치: 외부 오브젝트 스토리지 +- MinIO 버킷 데이터: 주기적 외부 스토리지 동기화 +- 설정 파일과 compose 파일: Git 또는 암호화된 설정 저장소 별도 보관 + +복구 목표: + +- RPO 목표: 5분에서 15분 +- RTO 목표: 1시간 이내 + +권장 운영 습관: + +- 월 1회 복구 리허설 +- 최소 스테이징 환경으로 복원 테스트 +- 백업 성공 여부만 보지 말고 실제 복구 성공 여부 확인 + +## 13. 보안 권장안 + +초기 사이드 프로젝트라도 아래는 반드시 한다. + +- root 비밀번호 SSH 로그인을 끄고 키 기반 인증으로 전환 +- fail2ban 또는 동등한 차단 장치 적용 +- firewalld/ufw로 외부 포트 최소화 +- Docker secrets 또는 최소한 권한 제한된 env 파일 사용 +- DB/Redis/MinIO 비밀번호 분리 +- TLS 강제 +- 관리자 계정 MFA 고려 +- 민감 로그 마스킹 + +애플리케이션 보안 체크포인트: + +- rate limiting +- 대화방 멤버십 권한 검사 +- 첨부파일 MIME 및 확장자 검증 +- 업로드 크기 제한 +- 악성 파일 검사 훅 준비 +- 세션 폐기와 토큰 로테이션 + +## 14. 스케일링 경로 + +### 단계 1. MVP + +구성: + +- 단일 VPS +- 단일 `messenger-app` +- 단일 `messenger-worker` +- PostgreSQL 단일 인스턴스 +- Redis 단일 인스턴스 +- MinIO 단일 인스턴스 + +적합한 상황: + +- 개인 프로젝트 초기 +- 소수 사용자 +- 운영자가 혼자일 때 + +### 단계 2. 같은 아키텍처로 성능 확장 + +구성 변화: + +- 더 큰 VPS 또는 별도 앱 서버 +- `messenger-app` 복수 인스턴스 +- Redis 기반 세션 공유와 fan-out +- PostgreSQL 튜닝 및 읽기 부하 분산 준비 + +필수 전제: + +- WebSocket 세션 상태가 로컬 메모리에만 있지 않아야 함 +- outbox 기반 비동기 처리 구조가 이미 있어야 함 + +### 단계 3. 상태 저장 계층 분리 + +구성 변화: + +- PostgreSQL을 관리형 또는 별도 전용 서버로 이동 +- MinIO를 외부 오브젝트 스토리지로 이동 +- Redis를 별도 인스턴스로 분리 +- 앱은 여러 대로 수평 확장 + +이 단계에서 바뀌면 좋은 것: + +- 로드밸런서 도입 +- 무중단 배포 +- 관측성 경보 체계 정교화 + +### 단계 4. 더 큰 사용량 + +구성 변화: + +- 내부 이벤트 버스를 Redis pub/sub에서 `NATS JetStream`으로 교체 또는 병행 +- PostgreSQL 읽기 복제본 도입 +- 검색을 외부 검색엔진으로 분리 +- 대화방 파티셔닝 또는 사용자 샤딩 검토 + +Kafka는 이 프로젝트 규모에서 너무 이른 선택일 가능성이 높다. NATS JetStream이 운영 난이도와 기능 균형이 더 좋다. + +## 15. 마이그레이션 경로를 부드럽게 만드는 설계 포인트 + +초기부터 반드시 지켜야 하는 항목: + +- 메시지 ID는 시간 정렬 가능한 전역 고유값 사용 +- DB 마이그레이션 체계 도입 +- 아웃박스 패턴 도입 +- 첨부 저장소는 S3 호환 인터페이스 뒤에 숨기기 +- 세션/프레즌스는 Redis 기반으로 외부화 가능하게 만들기 +- API 응답과 WebSocket 이벤트에 버전 필드 두기 +- 기능 플래그 도입 + +이것만 지켜도 단일 VPS에서 시작한 시스템을 크게 깨지 않고 다음 단계로 옮길 수 있다. + +## 16. 자가 구현 서버 vs Matrix/기타 프로토콜 비교 + +### 16.1 이번 프로젝트의 권장 선택 + +이번 프로젝트는 `자가 구현 서버`가 더 적합하다. + +이유: + +- 카카오톡 PC 스타일 UX를 맞추려면 제품 흐름과 프로토콜을 세밀하게 제어해야 한다. +- Matrix는 강력하지만 연합, 브리지, 규격 준수, 이벤트 모델 이해 비용이 크다. +- 단일 VPS에서 개인 프로젝트로 운영하기에는 Synapse 계열은 무겁고 운영 난이도가 높다. +- Element류 생태계와 맞물리는 장점보다, 제품 개성과 속도에서 제약이 더 크게 느껴질 가능성이 높다. + +### 16.2 Matrix가 적합한 경우 + +- 오픈 프로토콜과 연합이 중요할 때 +- 외부 클라이언트 호환성이 중요할 때 +- 자체 프로토콜 설계보다 표준 기반 확장을 원할 때 + +### 16.3 기타 선택지 + +- XMPP: 성숙했지만 현대적인 제품 UX에 맞춘 구현에서는 다시 커스텀 계층이 많이 필요하다. +- MQTT: IoT와 경량 pub/sub에는 좋지만 메신저 영속 모델과 권한 모델이 핵심이 아니므로 주축으로는 비권장이다. + +결론: + +- MVP와 제품 완성도 우선이라면 자가 구현 +- 개방형 생태계 우선이라면 Matrix + +이번 케이스에서는 자가 구현이 맞다. + +## 17. 실제 구축 순서 권장안 + +1. 도메인과 서브도메인 구조 확정 +2. Docker Compose 스택 초안 구성 +3. PostgreSQL 스키마와 마이그레이션 체계 확정 +4. 인증/세션 모델 확정 +5. REST + WSS 이벤트 계약서 작성 +6. outbox 기반 메시지 저장/전달 흐름 구현 +7. Redis presence 및 세션 인덱스 도입 +8. MinIO 첨부 업로드 흐름 도입 +9. Prometheus/Loki/Grafana 기본 대시보드 구성 +10. 외부 백업 파이프라인 연결 +11. 장애 복구 리허설 +12. 부하 테스트 후 튜닝 + +## 18. 최종 합의안 + +가장 현실적이고 좋은 방향은 아래다. + +- 프로토콜은 `REST + WebSocket` +- 서버 구조는 `모놀리식 코드베이스 + app/worker 역할 분리` +- DB는 `PostgreSQL` +- 캐시/프레즌스는 `Redis` +- 첨부 저장소는 `MinIO(S3 호환)` +- 프록시는 `Caddy` +- 관측성은 `Prometheus + Loki + Grafana` +- 백업은 `외부 오브젝트 스토리지로 분리` +- 확장 경로는 `단일 VPS -> 앱 복제본 -> 상태 저장 계층 외부화 -> NATS/복제/샤딩` + +이 구조는 사이드 프로젝트의 실행 가능성과, 이후 실제 사용자가 붙었을 때의 확장 가능성 사이에서 가장 균형이 좋다. diff --git a/docs/archive/planning/06-quality-release-and-launch.md b/docs/archive/planning/06-quality-release-and-launch.md new file mode 100644 index 0000000..e7f6515 --- /dev/null +++ b/docs/archive/planning/06-quality-release-and-launch.md @@ -0,0 +1,427 @@ +# 품질 전략, 테스트 계획, 릴리즈 게이트, 텔레메트리, 베타 운영안 + +## 문서 목적 + +이 문서는 Windows PC 우선 메신저 사이드 프로젝트의 품질 확보 체계를 정의한다. 범위는 데스크톱 앱과 VPS 기반 채팅 백엔드를 함께 포함하며, 알파 단계부터 정식 런치 직전까지의 테스트, 관측성, 장애 대응, 승인 기준, 최종 마감 체크리스트를 다룬다. + +이 제안은 최소 6개 관점의 합의안으로 본다. + +- QA 리드: 회귀 방지와 출시 게이트 설계 +- Windows 데스크톱 엔지니어: 설치, 업데이트, 자원 사용량, 크래시 대응 +- 백엔드/SRE: VPS 안정성, 장애 복구, 로그와 모니터링 +- UX 리서처: 대화 흐름, 인지 부하, 사용성 검증 +- 보안/프라이버시 담당: 로그 최소 수집, 민감정보 처리 원칙 +- 릴리즈 매니저: 베타 롤아웃, 빌드 승격, 런치 준비 + +## 1. 품질 전략 원칙 + +### 1.1 제품 품질의 정의 + +이 프로젝트의 품질은 단순히 버그 수가 적은 상태가 아니라 아래 조건을 동시에 만족하는 상태로 정의한다. + +- 메신저의 핵심 루프가 끊기지 않는다. +- 느린 네트워크와 일시적 장애에서도 대화 맥락이 유지된다. +- Windows PC 환경에서 설치, 실행, 업데이트, 복구가 자연스럽다. +- UI가 카카오톡 PC 사용 경험을 참고하되, 더 세련되고 명확한 피드백을 제공한다. +- 텔레메트리와 크래시 리포트가 문제를 재현 가능한 수준까지 설명한다. +- 정식 출시 직전에는 치명적 버그를 막는 게이트가 자동화와 수동 검수 양쪽에 존재한다. + +### 1.2 품질 우선순위 + +우선순위는 아래 순서로 둔다. + +1. 메시지 손실 방지 +2. 로그인/세션 안정성 +3. 실시간 연결 복구 +4. UI 응답성과 스크롤/입력 안정성 +5. 설치, 자동 업데이트, 재실행 복구 +6. 미세한 시각 완성도와 인터랙션 폴리시 + +### 1.3 품질 목표 + +초기 런치 기준 목표치는 아래처럼 잡는다. + +- 앱 크래시 없는 세션 비율: 99.5% 이상 +- 메시지 전송 성공률: 99.9% 이상 +- 네트워크 일시 단절 후 자동 재연결 성공률: 95% 이상 +- 앱 콜드 스타트에서 대화 목록 표시까지: 일반 환경 기준 3초 이내 +- 기본 채팅 입력 반응 시간: 체감상 지연 없이 100ms 이하 목표 +- 중대한 회귀 버그가 있는 빌드는 외부 베타로 승격 금지 + +## 2. 테스트 전략 전체 구조 + +테스트는 아래 4층으로 설계한다. + +- Unit Test: 메시지 상태 전이, 입력 검증, 포맷터, 상태 관리, 동기화 로직 +- Integration Test: 데스크톱 앱 모듈 간 연계, 로컬 저장소, 인증, 소켓/HTTP 결합 +- End-to-End Test: 실제 사용자 시나리오를 앱 + VPS 환경에서 검증 +- Manual UX Check: 시각 완성도, 감정선, 읽기 흐름, 미세한 피드백 점검 + +자동화만으로는 메신저 품질이 충분히 보장되지 않으므로, UI와 상호작용의 감성적 완성도는 반드시 수동 검수를 포함한다. + +## 3. 단계별 테스트 계획 + +## 3.1 Unit Test 범위 + +가장 먼저 안정화해야 할 단위는 아래와 같다. + +- 메시지 모델: 생성, 수정, 삭제, 전달 상태, 읽음 상태, 실패 상태 전이 +- 대화방 정렬 로직: 최신 메시지 기준 정렬, 고정 대화방 우선 노출 +- 검색 로직: 채팅방 이름, 메시지 텍스트, 사용자 이름 검색 결과 일관성 +- 입력창 로직: 멀티라인 입력, 엔터 전송/줄바꿈 규칙, IME 조합 중 입력 처리 +- 파일 첨부 로직: 허용 포맷, 크기 제한, 업로드 대기/실패 상태 +- 세션 상태: 로그인 유지, 토큰 만료, 재인증 트리거 +- 재연결 전략: backoff, 중복 연결 방지, 연결 상태 배지 표시 조건 +- 알림 로직: 포그라운드/백그라운드 조건별 알림 발송 정책 +- 로컬 캐시: 최근 대화, 프로필, 읽음 위치 복원 + +Unit Test의 기준은 단순 커버리지 수치보다, 상태 전이가 많은 로직을 빠짐없이 명세하는 데 둔다. + +## 3.2 Integration Test 범위 + +데스크톱 앱과 백엔드 경계, 또는 앱 내부 모듈 경계에서 아래를 검증한다. + +- 로그인 후 사용자 정보, 대화 목록, 최근 메시지 초기 로드 +- WebSocket 연결 수립 전후의 REST fallback 동작 +- 네트워크 재연결 이후 누락 메시지 동기화 +- 이미지/파일 업로드 후 메시지 항목과 미디어 URL 연결 +- 읽음 처리 전송 후 상대/서버 상태 반영 +- 알림 클릭 시 정확한 대화방으로 포커스 이동 +- 로컬 저장소 손상 또는 오래된 캐시 버전에서 안전한 복구 +- 앱 업데이트 후 기존 세션과 캐시 유지 여부 +- 서버 응답 지연, 중복 응답, 순서 뒤집힘 상황에서 UI 일관성 유지 + +## 3.3 End-to-End Test 시나리오 + +필수 E2E 시나리오는 아래를 포함한다. + +- 신규 사용자 로그인 후 첫 채팅 시작 +- 기존 사용자 재실행 후 대화 목록 복원 +- 1:1 채팅 메시지 송수신 +- 다자간 채팅방 입장, 메시지 수신, 읽음 상태 변화 +- 이미지 첨부, 업로드 완료, 실패 후 재시도 +- 앱 재실행 중 미전송 메시지 복구 +- 네트워크 끊김 중 입력/전송 시도 후 복구 +- 중복 로그인 또는 세션 만료 상황 처리 +- 검색에서 채팅방 진입 후 뒤로 이동 +- 알림 클릭으로 특정 메시지 문맥 진입 +- 오래된 메시지 스크롤 페이징 +- 매우 긴 대화방 이름, 긴 메시지, 이모지, 한글 IME 혼합 입력 +- 서버 재시작 중 클라이언트 복구 + +E2E는 로컬 스텁 환경만으로 끝내지 말고, 실제 VPS 스테이징 환경에서도 주기적으로 돌려야 한다. + +## 4. 수동 UX 검수 계획 + +자동 테스트가 통과해도 아래 항목은 사람이 직접 판단해야 한다. + +- 첫 실행 시 정보 구조가 즉시 이해되는가 +- 좌측 대화 목록, 중앙 대화 영역, 상단 상태 정보의 시선 흐름이 자연스러운가 +- 선택 상태, hover, unread, muted, pinned 상태가 한눈에 구분되는가 +- 메시지 입력창의 높이 변화와 스크롤 이동이 거슬리지 않는가 +- 새 메시지 도착 시 시각적 피드백이 과하지 않으면서 놓치지 않게 설계됐는가 +- 읽음 표시, 전송 중, 실패 상태의 의미가 직관적인가 +- 설정 화면이 과도하게 복잡하지 않은가 +- 오류 문구가 사용자 책임으로 들리지 않고, 해결 가능성을 제시하는가 +- 폰트 렌더링, 한글 자간, line-height, 아이콘 선 굵기가 일관적인가 +- 다크/라이트 모드가 있다면 대비와 시선 분리가 안정적인가 + +수동 UX 검수는 최소 3종 장비 조합으로 수행한다. + +- 일반적인 FHD 노트북 +- 고배율 디스플레이가 적용된 고해상도 Windows PC +- 저사양 또는 메모리 제약이 있는 테스트 장비 + +## 5. 네트워크 복원력 시나리오 + +메신저는 정상 네트워크보다 비정상 네트워크에서 품질 차이가 크게 드러난다. 아래 시나리오는 별도 회복력 테스트 묶음으로 관리한다. + +### 5.1 연결 품질 저하 + +- 고지연 환경 +- 패킷 손실 환경 +- 업로드만 느린 환경 +- 순간적인 DNS 실패 +- TLS 핸드셰이크 지연 + +### 5.2 연결 중단과 복구 + +- Wi-Fi 꺼짐 후 재연결 +- 노트북 절전 진입 후 복귀 +- VPN on/off 전환 +- 사내망/공용망 전환 +- 백엔드 WebSocket 프로세스 재시작 +- VPS 재부팅 + +### 5.3 동기화 이상 + +- 메시지 ACK 지연 +- 서버는 수신했지만 클라이언트가 ACK를 못 받은 상태 +- 동일 메시지 중복 수신 +- 오래된 이벤트가 늦게 도착 +- 읽음 이벤트가 메시지보다 먼저 도착 + +### 5.4 기대 동작 + +각 네트워크 이상 시나리오에서 앱은 아래 동작을 만족해야 한다. + +- 연결 상태를 숨기지 않고 명확하게 알려준다. +- 전송 실패 메시지는 사라지지 않고 재시도 가능해야 한다. +- 재연결 이후 중복 메시지를 만들지 않아야 한다. +- 사용자 입력은 가능한 한 보존되어야 한다. +- 회복 후에는 최신 상태와의 차이를 자동 동기화해야 한다. + +## 6. 텔레메트리 전략 + +텔레메트리는 문제를 빨리 발견하고 우선순위를 정하기 위한 최소 수집 원칙으로 설계한다. 메시지 본문, 파일 내용, 개인식별 가능 정보는 수집하지 않는다. + +### 6.1 핵심 이벤트 + +- 앱 실행, 종료, 비정상 종료 +- 로그인 성공/실패 +- 대화 목록 로드 성공/실패/지연 +- 채팅방 진입 +- 메시지 전송 시도/성공/실패 +- 파일 업로드 시도/성공/실패 +- WebSocket 연결, 끊김, 재연결 +- API 오류 코드 집계 +- 업데이트 다운로드/적용 성공 여부 + +### 6.2 핵심 지표 + +- DAU/WAU +- 세션 길이 +- 대화방 진입 대비 실제 메시지 전송 비율 +- 메시지 전송 실패율 +- reconnect 횟수와 성공률 +- 앱 버전별 크래시율 +- API 엔드포인트별 실패율과 p95 응답 시간 +- 특정 릴리즈 이후 회귀 지표 변화 + +### 6.3 대시보드 구성 + +릴리즈 직후 가장 먼저 보는 대시보드는 아래 4개다. + +- 안정성 대시보드: 크래시율, 비정상 종료, 재실행 루프 +- 메시징 대시보드: 전송 성공률, ACK 지연, 중복 메시지 감지 +- 연결성 대시보드: WebSocket 단절, 재연결 성공률, 서버 에러율 +- 릴리즈 대시보드: 버전별 설치 성공률, 업데이트 적용 성공률, 롤백 필요 신호 + +## 7. 크래시 핸들링 전략 + +### 7.1 클라이언트 + +- 비정상 종료 시 다음 실행에서 안전 복구 모드 진입 여부를 판단한다. +- 크래시 리포트에는 앱 버전, OS 버전, 메모리 상태, 직전 화면, 최근 오류 범주를 포함한다. +- 민감정보와 메시지 본문은 제외한다. +- 크래시 직전 사용자가 작성 중이던 미전송 텍스트는 가능한 범위에서 복구한다. +- 반복 크래시가 특정 화면 진입에서 발생하면 해당 기능을 임시 비활성화할 수 있어야 한다. + +### 7.2 서버 + +- 프로세스 재시작 정책을 설정한다. +- 앱 서버, DB, 스토리지, 리버스 프록시 각각의 헬스 체크를 분리한다. +- 장애 시 최근 배포 이력과 리소스 사용량을 즉시 연동 확인 가능해야 한다. +- 치명 장애 발생 시 운영 알림 채널로 즉시 통지한다. + +### 7.3 크래시 triage 우선순위 + +- P0: 앱 실행 불가, 로그인 불가, 메시지 손실 가능성 +- P1: 반복 크래시, 재연결 실패, 파일 업로드 핵심 기능 불가 +- P2: 특정 화면 진입 시 크래시, 우회 가능하지만 경험 훼손 큼 +- P3: 드문 환경에서의 비핵심 기능 크래시 + +## 8. 릴리즈 단계와 게이트 + +릴리즈는 `Alpha -> Closed Beta -> Open Beta -> RC -> Launch` 흐름으로 관리한다. + +### 8.1 Alpha + +목적: + +- 핵심 채팅 루프가 성립하는지 확인 +- 구조적 결함 조기 발견 + +대상: + +- 개발자 본인과 내부 소수 테스터 + +필수 기준: + +- 로그인, 대화 목록, 메시지 송수신, 기본 재연결이 동작 +- 치명 크래시가 재현성 있게 남아있지 않음 +- 텔레메트리와 크래시 리포트 수집 가능 + +차단 조건: + +- 메시지 유실 재현 +- 세션 꼬임 +- 앱 시작 불가 + +### 8.2 Closed Beta + +목적: + +- 다양한 Windows 환경에서 회귀와 설치/업데이트 문제 발견 +- 사용성 불만과 혼란 포인트 수집 + +대상: + +- 신뢰 가능한 외부 사용자 20명에서 100명 규모 + +필수 기준: + +- 자동 업데이트 또는 업데이트 유도 플로우 안정화 +- 주요 E2E와 네트워크 복원력 시나리오 통과 +- 고우선순위 이슈 대응 프로세스 마련 + +차단 조건: + +- 버전 업 이후 캐시 손상 +- 특정 GPU/해상도 조합에서 UI unusable +- 알림/포커스 이동이 신뢰할 수 없는 수준 + +### 8.3 Open Beta + +목적: + +- 실제 사용 패턴과 부하 기반 안정성 검증 +- VPS 백엔드의 운영 내구성 확인 + +대상: + +- 초대 또는 신청 기반의 확장된 사용자군 + +필수 기준: + +- 크래시율과 전송 실패율이 목표치 근처에서 안정화 +- 서버 모니터링, 알림, 백업, 장애 대응 문서화 완료 +- 주요 UX 불만의 상위 항목 정리와 개선 반영 + +차단 조건: + +- 피크 시간대에서 서버 병목 +- 업로드/다운로드 기능의 잦은 실패 +- 보안/개인정보 위험 신호 + +### 8.4 RC + +목적: + +- 정식 출시 후보 빌드 확정 + +필수 기준: + +- P0, P1 이슈 0건 +- 승인된 예외 목록만 남아있음 +- 회귀 테스트, 수동 UX 체크, 설치/업데이트 검증 완료 +- 릴리즈 노트, 알려진 이슈, 지원 대응 문안 준비 완료 + +### 8.5 Launch + +목적: + +- 안전한 공개 전환과 초기 운영 안정화 + +필수 기준: + +- 모니터링 대시보드 활성화 +- 운영 온콜 또는 대응 시간대 확보 +- 롤백 경로와 이전 안정 버전 보관 +- 초기 72시간 관찰 계획 확정 + +## 9. 베타 롤아웃 전략 + +### 9.1 배포 원칙 + +- 한 번에 전체 공개하지 않고 점진적으로 확장한다. +- 데스크톱 앱 버전과 서버 릴리즈를 명확히 매핑한다. +- 서버 측 기능 플래그로 위험 기능을 단계적으로 연다. + +### 9.2 추천 롤아웃 흐름 + +1. 내부 알파 100% +2. 클로즈드 베타 10명 +3. 클로즈드 베타 30명 +4. 클로즈드 베타 100명 +5. 오픈 베타 제한 공개 +6. 정식 출시 전 RC 고정 +7. 런치 후 초기 사용자군 10% 수준 점진 확대 + +### 9.3 베타 피드백 수집 + +- 앱 내 피드백 진입점 제공 +- 이슈 제보 시 자동 첨부 가능한 진단 요약 제공 +- 설문은 짧게 유지하고, 채팅 사용 맥락 기반 질문으로 설계 +- 정량 지표와 정성 피드백을 따로 보지 말고 함께 해석 + +## 10. 승인 기준과 완료 정의 + +### 10.1 기능 승인 기준 + +한 기능은 아래를 만족해야 완료로 본다. + +- 명세된 핵심 시나리오가 자동화 테스트 또는 재현 가능한 체크리스트로 검증됨 +- 실패 상태 UI와 문구가 존재함 +- 텔레메트리 이벤트가 연결됨 +- 접근성 기본 기준을 위반하지 않음 +- Windows 배율, 창 크기 변화, 포커스 이동에서 깨지지 않음 + +### 10.2 릴리즈 승인 기준 + +릴리즈는 아래를 모두 충족해야 승격한다. + +- P0, P1 미해결 이슈 없음 +- 최근 변경 범위에 대한 회귀 테스트 완료 +- 설치, 실행, 로그인, 메시지 송수신, 파일 첨부, 재연결 수동 검수 완료 +- VPS 서버 상태, 디스크 사용량, DB 백업, 스토리지 상태 확인 완료 +- 모니터링과 알림이 실제로 동작함 + +## 11. 최종 폴리시와 마감 체크리스트 + +### 11.1 데스크톱 앱 최종 체크 + +- 설치/삭제가 정상 동작한다. +- 첫 실행 경험이 매끄럽다. +- 업데이트 후 설정, 세션, 캐시가 의도대로 유지된다. +- 창 최소화, 복원, 다중 모니터 이동이 안정적이다. +- 알림 클릭 시 정확한 대화방으로 이동한다. +- 입력창, 스크롤, 붙여넣기, 드래그 앤 드롭이 예상대로 동작한다. +- 고배율 디스플레이에서 흐릿한 요소가 없다. +- 폰트, 아이콘, 간격, hover, selection이 일관적이다. + +### 11.2 백엔드/VPS 최종 체크 + +- 프로세스 자동 시작과 재시작 정책이 확인됐다. +- 리버스 프록시, 앱, DB, 스토리지 헬스 체크가 정상이다. +- 백업과 복원 절차가 실제 검증됐다. +- 로그 보존 정책과 디스크 사용량 경보가 설정됐다. +- TLS, 도메인, 인증서 자동 갱신 상태가 확인됐다. +- 서버 재기동 후 클라이언트 재연결이 정상 동작한다. + +### 11.3 런치 72시간 체크 + +- 크래시율, 전송 실패율, 재연결 실패율을 2시간 단위로 본다. +- 상위 오류 5개를 매일 triage 한다. +- 사용자 피드백을 UX, 성능, 안정성으로 분류한다. +- 심각한 회귀가 있으면 기능 플래그 off 또는 롤백을 즉시 검토한다. + +## 12. 권장 운영 리듬 + +추천 리듬은 아래와 같다. + +- 매일: 크래시/오류/전송 실패 지표 확인 +- 주 2회: 회귀 테스트와 베타 피드백 정리 +- 주 1회: 릴리즈 후보 점검 회의 +- 베타 기간 중: 매 릴리즈마다 수동 UX 검수 세션 수행 + +## 13. 최종 권고 + +이 프로젝트의 성공 여부는 UI를 얼마나 비슷하게 만들었는지보다, 사용자가 불안 없이 메시지를 보내고 다시 돌아왔을 때 맥락이 보존되는지에 달려 있다. 따라서 출시 게이트는 시각적 유사성보다 메시지 신뢰성, 재연결 복원력, 설치/업데이트 안정성, 관측성 완성도를 우선해야 한다. + +정식 출시 직전에는 새로운 기능 추가를 멈추고 아래 4가지만 집중하는 것이 가장 낫다. + +- 크래시 제거 +- 네트워크 복원력 보강 +- 텔레메트리와 운영 대시보드 정제 +- UI 폴리시와 마이크로 인터랙션 최종 다듬기 diff --git a/docs/archive/planning/README.md b/docs/archive/planning/README.md new file mode 100644 index 0000000..bbd573e --- /dev/null +++ b/docs/archive/planning/README.md @@ -0,0 +1,14 @@ +# Messenger Planning Set + +이 폴더는 Windows PC 기준 개인 메신저 사이드 프로젝트의 기획 문서를 모아두는 planning set이다. + +문서 목록: + +1. [01-backend-platform-architecture.md](01-backend-platform-architecture.md) + 채팅 서버 아키텍처, VPS 배포 전략, 데이터/스토리지, 관측성, 백업, 확장 및 마이그레이션 전략 +2. [windows-desktop-client-architecture.md](windows-desktop-client-architecture.md) + Windows-first 데스크톱 기술 스택 선정, WinUI 3 vs 대안 비교, 패키징, 오프라인 캐시, 알림, 보안 경계, 단계별 구현 전략 +3. [06-quality-release-and-launch.md](06-quality-release-and-launch.md) + 품질 전략, 테스트 계획, 릴리즈 게이트, 텔레메트리, 베타 운영안 + +이 폴더는 제품 기획 세트 중 실행/운영 관점 문서를 모아둔다. 백엔드, 플랫폼, 품질, 릴리즈, 운영 문서를 계속 추가한다. diff --git a/docs/archive/planning/windows-desktop-client-architecture.md b/docs/archive/planning/windows-desktop-client-architecture.md new file mode 100644 index 0000000..aeebb26 --- /dev/null +++ b/docs/archive/planning/windows-desktop-client-architecture.md @@ -0,0 +1,489 @@ +# Windows 데스크톱 클라이언트 기술/아키텍처 결정서 + +## 문서 목적 + +이 문서는 Windows PC 기준으로 먼저 개발하는 메신저 데스크톱 앱의 기술 스택과 애플리케이션 구조를 결정하기 위한 기준 문서다. 목표는 "카카오톡 PC에서 기대하는 속도, 상시 실행성, 익숙한 생산성"을 유지하면서도 더 세련된 UI와 안정적인 확장 구조를 확보하는 것이다. + +범위는 데스크톱 클라이언트에 한정한다. 서버 프로토콜 상세, 운영 인프라, 모바일 클라이언트는 별도 문서에서 다룬다. + +## 최종 권고안 + +Windows 1차 출시 기준 권고 조합은 아래와 같다. + +- UI 프레임워크: `WinUI 3 + Windows App SDK` +- 언어/런타임: `C# + .NET 8` +- 아키텍처 패턴: `MVVM + 기능별 모듈 구조(feature-first)` +- 상태 관리: `CommunityToolkit.Mvvm` 기반의 `ViewModel + Domain Store + Repository` 구조 +- 로컬 저장소: `SQLite` 중심의 오프라인 우선 캐시 +- 실시간 통신 추상화: `WebSocket` 기반 이벤트 스트림 + `HTTPS` 기반 명령/업로드 API +- 패키징: `최종 배포는 MSIX 패키지드 앱`을 기본으로 하고, 내부 개발 루프에서는 필요 시 unpackaged 디버그 프로필 병행 +- 알림: `Windows toast notification + tray resident mode` +- 자동 업데이트: `MSIX App Installer 업데이트 피드` +- 비밀정보 저장: `Windows DPAPI/PasswordVault 계열 저장소` + +핵심 판단은 단순하다. 이 프로젝트는 처음부터 Windows에서 가장 자연스럽게 느껴져야 하고, 메신저 특유의 "항상 켜져 있는 앱" 경험이 중요하다. 그 기준에서는 Electron보다 WinUI 3가 유리하고, WPF보다 미래 지향적이며, MAUI/Avalonia보다 Windows 통합 품질이 높다. + +## WinUI 3 vs 대안 비교 + +### 1. WinUI 3 + +가장 균형이 좋다. + +- 장점 +- Windows 11 감성에 가장 잘 맞는 기본 컨트롤, 타이포, 재질, 입력 체계를 바로 활용할 수 있다. +- 알림, 앱 아이덴티티, 윈도우 관리, 테마 대응 같은 Windows 통합이 자연스럽다. +- C#/.NET 생태계를 그대로 쓰면서 성능, 메모리, 유지보수 측면에서 Electron보다 유리하다. +- 향후 Windows 전용 고급 기능을 붙일 때 우회가 적다. + +- 단점 +- WPF보다 생태계와 레퍼런스가 덜 성숙했다. +- 일부 고급 데스크톱 패턴, 특히 tray, title bar 세부 제어, 복잡한 virtualization 튜닝은 Win32 interop 이해가 필요하다. + +### 2. WPF + +실용성은 높지만 최종 권고안은 아니다. + +- 장점 +- 데스크톱 안정성과 서드파티 생태계가 매우 강하다. +- tray, 윈도우 제어, 업데이트, 커스텀 chrome 같은 데스크톱 전통 과제는 가장 익숙하게 풀 수 있다. + +- 단점 +- 기본 인상이 오래되었고, "트렌디한 Windows 앱" 감성을 얻으려면 커스텀 비용이 커진다. +- 지금 새로 시작하는 Windows 전용 메신저라면 장기 방향성에서 WinUI 3보다 매력이 약하다. + +### 3. Electron + +초기 생산성은 좋지만 이 프로젝트의 최적해는 아니다. + +- 장점 +- 웹 인력 전환이 쉽고, 풍부한 UI 라이브러리를 바로 쓸 수 있다. +- 크로스플랫폼 확장성이 좋다. + +- 단점 +- 메신저처럼 항상 켜 두는 앱에서 메모리/배터리/네이티브 감성 측면 손해가 크다. +- Windows 통합이 "가능"한 수준이지 "자연스러운 기본값"은 아니다. +- 보안 경계 관리가 더 까다롭다. + +### 4. Avalonia + +기술적으로 괜찮지만 이번 목표와는 다르다. + +- 장점 +- 크로스플랫폼을 염두에 둔 C# 선택지 중 가장 실전적이다. + +- 단점 +- Windows 퍼스트 제품에서 필요한 OS 통합, 알림, 설치체험, 앱 정체성 측면은 WinUI 3보다 약하다. + +### 5. .NET MAUI + +권장하지 않는다. + +- 장점 +- 모바일과 공유할 수 있는 발판은 있다. + +- 단점 +- Windows 데스크톱 완성도가 핵심인 메신저에는 맞지 않는다. +- 데스크톱 UX, 창 관리, 리스트 성능, 윈도우 느낌 모두 전용 데스크톱 프레임워크보다 불리하다. + +## 패키징 선택 + +최종 결론은 `MSIX 패키지드 앱`이다. + +이유는 아래와 같다. + +- Windows 알림과 앱 아이덴티티가 안정적이다. +- 설치/제거가 깔끔하고 잔여 파일 문제가 적다. +- 시작 메뉴, 프로토콜 활성화, 알림 활성화, 권한 모델을 제품답게 다루기 좋다. +- 장기적으로 베타/정식 배포 체계를 운영하기 쉽다. + +다만 개발 단계에서는 두 가지 현실을 인정해야 한다. + +- 내부 개발 속도만 보면 unpackaged 디버그가 더 편할 수 있다. +- 비공개 테스트 배포에서 인증서/설치 체인이 번거로울 수 있다. + +실행 전략은 이렇게 잡는다. + +- 개발 초기: 개발기 로컬에서는 빠른 디버그 루프를 우선한다. +- 비공개 알파 이후: 배포 검증은 반드시 MSIX 기준으로 한다. +- 외부 배포 시점: MSIX + App Installer를 공식 릴리스 경로로 고정한다. + +별도 exe 인스톨러 + 자체 업데이터 조합은 지금 단계에서는 피한다. 메신저 제품은 설치 체인보다 상시 안정성과 Windows 통합이 더 중요하다. + +## 앱 셸 아키텍처 + +메신저는 일반 CRUD 앱이 아니다. 창이 곧 제품이다. 셸 설계가 곧 사용자 경험의 절반이다. + +권장 구조는 `단일 메인 셸 + 필요 시 보조 창` 구조다. + +- 메인 창 +- 좌측 고정 내비게이션 +- 채팅 목록 +- 선택된 대화 뷰 +- 우측 상세 패널은 필요 시 확장 + +- 보조 창 +- 이미지 뷰어 +- 파일 전송 상세 +- 설정 +- 로그인/계정 전환 +- 향후 통화 팝업 + +셸 원칙은 다음과 같다. + +- 기본은 단일 창 경험으로 시작한다. +- 대화창을 무제한 분리하는 기능은 초기 버전에 넣지 않는다. +- 창을 여러 개 띄우는 순간 동기화, 포커스, 알림 중복, 메모리 문제가 같이 커진다. +- 대신 "이미지 뷰어", "환경설정", "별도 팝업 업무창" 정도만 보조 창으로 분리한다. + +탐색 방식은 페이지 네비게이션보다 `상태 전환형 셸`이 더 적합하다. + +- 채팅 목록과 대화 뷰는 페이지 이동보다 같은 셸 내에서 컨텍스트만 바뀌는 구조가 낫다. +- 좌측 메뉴는 `채팅`, `친구`, `오픈채널/서버형 공간(향후)`, `설정` 정도로 제한한다. +- 메신저에서 과도한 딥 네비게이션은 UX를 해친다. + +## 상태 관리 전략 + +권장 모델은 `가벼운 MVVM + 도메인 스토어`다. + +전역 단일 상태 저장소 하나로 모든 것을 넣는 구조는 피한다. 메신저는 실시간 이벤트, 임시 입력 상태, 오프라인 큐, 동기화 상태가 얽혀서 거대 스토어가 금방 망가진다. + +추천 계층은 아래와 같다. + +- View +- ViewModel +- Feature Store +- Repository +- Transport/API Client +- Local DB/Cache + +각 계층의 책임은 다음과 같다. + +- ViewModel +- 화면 단위 상태만 가진다. +- 선택된 채팅, 입력창 포커스, 필터 텍스트, 패널 열림 여부 같은 UI 상태를 담당한다. + +- Feature Store +- 기능 단위의 세션 상태를 가진다. +- 예: `ChatListStore`, `ConversationStore`, `PresenceStore`, `NotificationStore` +- 서버 이벤트와 로컬 변경을 합쳐 화면에 안정적으로 제공한다. + +- Repository +- 로컬 DB와 네트워크를 조합한다. +- 예: "메시지 전송", "대화방 페이지 로드", "읽음 상태 반영", "첨부 업로드"를 트랜잭션처럼 묶는다. + +전역 공유가 필요한 상태는 아래 정도로 제한한다. + +- 현재 로그인 세션 +- 연결 상태 +- unread 총합 +- 활성 워크스페이스/계정 +- 공통 설정 + +## 로컬 캐시와 오프라인 동기화 + +메신저는 네트워크가 흔들려도 "쓸 수 있어 보이는 느낌"이 중요하다. 그래서 `SQLite를 단순 캐시가 아니라 로컬 소스 오브 트루스에 가깝게` 써야 한다. + +권장 원칙은 아래와 같다. + +- 앱 진입 시 채팅 목록은 로컬 DB에서 먼저 그린다. +- 선택한 대화의 최근 메시지도 먼저 로컬에서 즉시 보여 준다. +- 서버 응답은 그 뒤에 덮어쓰거나 이어 붙인다. +- 전송 버튼을 누르면 서버 성공 전에도 로컬에 임시 메시지를 만들어 즉시 표시한다. +- 서버 ack를 받으면 임시 ID를 서버 ID로 치환하고 상태를 정정한다. + +로컬 DB에 최소한 있어야 하는 도메인은 아래와 같다. + +- 계정/세션 메타데이터 +- 친구/프로필 +- 채팅방 목록 +- 채팅방 멤버십 +- 메시지 본문 +- 메시지 전송 상태 +- 읽음 상태 +- 첨부파일 메타데이터 +- 업로드/다운로드 작업 큐 +- 임시 저장 중인 draft +- 사용자 설정 + +동기화는 `cursor 기반 점진 동기화`를 전제로 설계한다. + +- 채팅 목록용 cursor +- 대화방별 메시지 cursor +- 읽음/반응/멤버 변경용 증분 이벤트 + +오프라인 큐에 들어갈 항목은 제한한다. + +- 텍스트 전송 +- 읽음 상태 보고 +- 반응 추가/취소 +- 첨부 업로드 예약 + +계정 설정 변경이나 대규모 프로필 변경은 온라인 상태에서만 처리하는 편이 안전하다. + +## 미디어 처리 전략 + +초기부터 "무거운 클라이언트"가 되지 않도록 범위를 통제해야 한다. + +원칙은 서버가 할 수 있는 무거운 일은 서버에서 처리하고, 클라이언트는 표시와 경량 캐시에 집중하는 것이다. + +권장 구성은 아래와 같다. + +- 이미지 +- 원본과 썸네일을 분리 관리한다. +- 리스트에서는 항상 썸네일/축소본만 사용한다. +- 전체 보기 시에만 고해상도 리소스를 읽는다. + +- 동영상 +- 채팅 리스트/대화창에는 포스터 프레임 중심으로 표시한다. +- 초기 버전에서는 자동 재생을 금지한다. +- 인라인 편집/트랜스코딩은 넣지 않는다. + +- 파일 +- 다운로드 전에는 메타데이터만 유지한다. +- 파일 열기는 OS 기본 연결 프로그램에 위임한다. +- 악성 확장자/이중 확장자 표시는 UI에서 명확히 한다. + +- 음성/녹음 +- 초기 MVP에는 제외하는 편이 낫다. +- 넣더라도 녹음, 재생, 업로드를 별도 기능군으로 격리한다. + +미디어 캐시는 계층화한다. + +- 메모리 캐시: 현재 화면에 보이는 이미지 +- 디스크 캐시: 최근 본 이미지/썸네일 +- 영구 보관: 사용자가 저장한 다운로드 파일만 + +클라이언트에서 FFmpeg 같은 대형 의존성을 너무 일찍 넣지 않는다. 초기에는 서버 썸네일 생성 + 클라이언트 표시만으로 충분하다. + +## 알림 전략 + +권장 조합은 `toast notification + 앱 내부 배너 + tray unread 표시`다. + +필수 시나리오는 아래와 같다. + +- 앱이 최소화되어 있거나 뒤에 있을 때 새 메시지 toast +- 앱이 포그라운드일 때는 toast 대신 인앱 배너 또는 해당 대화 강조 +- 알림 클릭 시 해당 대화로 정확히 이동 +- 중복 toast 방지 +- mute된 방, 현재 보고 있는 방, 조용한 시간대 예외 처리 + +메신저는 "창 닫기 = 종료"보다 "창 닫기 = tray 상주"가 더 자연스럽다. + +그래서 초기 설계부터 아래를 반영한다. + +- 우상단 X 클릭 시 기본 동작을 `tray 최소화`로 둘지 정책 결정 +- 완전 종료는 tray 메뉴 또는 설정에서 명시적으로 실행 +- 1회성 안내를 통해 사용자가 동작을 오해하지 않게 한다 + +tray 기능은 보조 요소가 아니라 핵심 상시성 UX다. + +## 자동 업데이트 + +정식 경로는 `MSIX App Installer 업데이트 피드`가 가장 낫다. + +추천 이유는 아래와 같다. + +- Windows 친화적인 업데이트 흐름을 유지할 수 있다. +- 설치 파일과 업데이트 정책이 한 체계 안에 있다. +- 알파, 베타, 스테이블 채널을 분리하기 좋다. + +운영 원칙은 아래처럼 잡는다. + +- `dev`, `beta`, `stable` 세 채널 분리 +- 앱 시작 시 무조건 업데이트하지 않고, 유휴 상태나 재시작 시점 반영 +- 강제 업데이트는 인증/프로토콜 호환성 깨질 때만 사용 + +업데이터를 별도 커스텀 프로세스로 처음부터 만들지 않는다. 메신저 본체가 안정화되기 전까지는 관리 포인트만 늘어난다. + +## 보안 경계 + +메신저 데스크톱 앱은 생각보다 공격면이 넓다. 첨부파일, 링크 프리뷰, 알림 활성화 인자, 로컬 캐시, 토큰 저장이 다 경계다. + +반드시 지킬 기준은 아래와 같다. + +- 인증 토큰은 평문 파일로 저장하지 않는다. +- refresh token이나 세션 비밀값은 OS 보호 저장소에 넣는다. +- 로컬 SQLite에는 필요한 최소 데이터만 저장한다. +- 서버에서 온 HTML/리치 콘텐츠를 앱 내부에서 임의 렌더링하지 않는다. +- 첨부파일은 "열기"와 "미리보기"의 경계를 분리한다. +- 로그에는 메시지 본문, 토큰, 첨부 URL 원문을 남기지 않는다. +- 크래시 리포트에도 PII 최소화 규칙을 둔다. + +프로세스 경계는 초기에 단순하게 가져간다. + +- 기본은 단일 앱 프로세스 +- 정말 필요할 때만 보조 프로세스 분리 + +보조 프로세스를 고려할 만한 경우는 아래다. + +- 대용량 미디어 전처리 +- 별도 업데이트/복구 헬퍼 +- 향후 화면 공유/통화 같은 고권한 기능 + +초기 MVP에서 브라우저 엔진 기반 렌더링, 스크립트 실행형 플러그인, 임의 확장 기능은 넣지 않는다. + +## IPC와 백그라운드 작업 + +초기 구조에서 필요한 IPC는 제한적이다. + +- 단일 인스턴스 유지 +- 두 번째 실행 요청이 오면 기존 인스턴스로 포커스 이동 +- 프로토콜 링크 또는 알림 활성화 인자를 기존 인스턴스로 전달 + +이 용도에는 `로컬 named pipe` 수준이면 충분하다. + +백그라운드 작업은 "모바일 같은 진짜 background task"보다 `tray 상주 앱` 개념으로 접근하는 것이 맞다. + +즉, 메신저 수신과 동기화는 아래를 기본 전제로 삼는다. + +- 사용자가 로그아웃하지 않은 이상 앱은 백그라운드에서 살아 있다 +- 창만 닫혀도 프로세스는 유지된다 +- 연결이 끊기면 지수 백오프로 재연결한다 + +Windows의 백그라운드 태스크 모델을 과용하지 않는다. 데스크톱 메신저에서는 주 실행 프로세스 상주가 더 단순하고 신뢰성이 높다. + +## 권장 폴더 구조 + +프로젝트는 `레이어만 예쁘게 나눈 구조`보다 `기능 단위로 이해되는 구조`가 유지보수에 낫다. + +권장 예시는 아래와 같다. + +- `src/App` +- 앱 진입점, 부트스트랩, 셸, 테마, 리소스, 공통 네비게이션 + +- `src/Features/Auth` +- 로그인, 세션 복구, 계정 전환 + +- `src/Features/ChatList` +- 채팅 목록, unread, 고정, 검색 진입 + +- `src/Features/Conversation` +- 메시지 목록, 입력창, 전송, 읽음 상태, 반응 + +- `src/Features/Contacts` +- 친구 목록, 프로필, 차단/숨김 + +- `src/Features/Notifications` +- toast, 배너, 알림 정책 + +- `src/Features/Settings` +- 일반, 알림, 파일, 계정, 실험 기능 + +- `src/Core` +- 공용 도메인 모델, 인터페이스, 결과 타입, 에러 규약 + +- `src/Infrastructure/Api` +- HTTP/WebSocket 클라이언트, DTO, 직렬화 + +- `src/Infrastructure/Persistence` +- SQLite, 마이그레이션, 리포지토리 구현 + +- `src/Infrastructure/Media` +- 썸네일, 다운로드, 임시 파일, 프리뷰 + +- `src/Infrastructure/Security` +- 토큰 저장, 암호화, 민감정보 정책 + +- `src/Infrastructure/Platform` +- tray, 프로토콜 활성화, 파일 연결, OS 통합 + +- `tests/Unit` +- ViewModel, Store, 도메인 규칙 + +- `tests/Integration` +- 로컬 DB, 동기화, 업로드/다운로드, reconnect 시나리오 + +핵심은 `Features`와 `Infrastructure`를 명확히 분리하는 것이다. 채팅 기능 코드가 SQLite 세부 구현이나 WebSocket payload 형태를 직접 알게 만들면 나중에 반드시 꼬인다. + +## 단계별 구현 전략 + +### Phase 0. 제품 골격 검증 + +목표는 "이 구조로 메신저 느낌이 나는가"만 확인하는 것이다. + +- 로그인 화면 +- 메인 셸 +- 채팅 목록 mock +- 대화창 mock +- 다크/라이트 테마 +- 창 최소화, 리사이즈, 기본 반응형 + +이 단계에서는 서버 연결보다 셸 품질을 먼저 본다. + +### Phase 1. 핵심 메시징 MVP + +실사용 가능한 최소 메신저를 만든다. + +- 계정 로그인/세션 복구 +- 채팅 목록 실데이터 +- 1:1 채팅 +- 텍스트 송수신 +- 읽음 상태 +- reconnect +- 로컬 캐시 부팅 + +여기서 가장 중요한 성공 조건은 "앱 재실행 후 바로 이전 대화가 보여야 한다"다. + +### Phase 2. 데스크톱다운 완성도 확보 + +카카오톡 PC 대체감을 만드는 구간이다. + +- tray 상주 +- toast 알림 +- 파일 첨부/다운로드 +- 전송 실패 재시도 +- draft 저장 +- 검색 진입 +- 채팅방 정렬/고정 + +이 단계에서 제품 체감 품질이 크게 올라간다. + +### Phase 3. 성능과 운영성 강화 + +- 대화방 수천 개, 메시지 수만 건에서도 부드러운 스크롤 +- 이미지 캐시 최적화 +- 크래시 리포트 +- 진단 로그 +- 업데이트 채널 운영 +- 설정 백업/복원 범위 정의 + +이 단계 없이 사용자 수를 늘리면 운영이 무너질 가능성이 높다. + +### Phase 4. 고급 기능 확장 + +이 단계는 코어가 안정화된 뒤에만 들어간다. + +- 멀티 계정 +- 그룹 채팅 고급 기능 +- 메시지 반응 +- 파일 히스토리 +- 통화/화면공유 +- 관리자/조직 기능 + +초기부터 이 기능을 욕심내면 아키텍처는 커 보이는데 제품은 느리고 불안정해진다. + +## 반드시 미루는 항목 + +초기 버전에서 제외하는 편이 좋은 항목들이다. + +- 플러그인 시스템 +- 내장 브라우저 기반 리치 콘텐츠 렌더링 +- 과한 애니메이션 +- 멀티 윈도우 대화창 자유 분리 +- 클라이언트 측 대형 미디어 트랜스코딩 +- 암호화 메신저 수준의 종단간암호화 복잡도 + +이런 요소는 제품 핵심이 아니라 복잡도 폭탄이 되기 쉽다. + +## 최종 판단 + +이 프로젝트의 최적 출발점은 `WinUI 3 + .NET 8 + MVVM + SQLite 기반 오프라인 우선 구조 + MSIX 배포`다. + +이 조합은 세련된 Windows UX, 메신저에 필요한 상시성, 실시간성, 설치 품질, 장기 유지보수의 균형이 가장 좋다. + +정리하면 아래 네 가지를 흔들지 않는 것이 중요하다. + +- Windows 전용 1차 제품답게 네이티브 경험을 우선한다. +- 로컬 캐시는 옵션이 아니라 제품 핵심으로 본다. +- tray, 알림, 재연결은 부가 기능이 아니라 메신저의 본체로 취급한다. +- 초기에 단일 창과 단순 프로세스 구조를 유지해 제품을 먼저 안정화한다. diff --git a/docs/archive/windows-messenger-planning/01-visual-interaction-direction.md b/docs/archive/windows-messenger-planning/01-visual-interaction-direction.md new file mode 100644 index 0000000..d0d34b6 --- /dev/null +++ b/docs/archive/windows-messenger-planning/01-visual-interaction-direction.md @@ -0,0 +1,359 @@ +# Windows-first 메신저 시각/인터랙션 설계 방향 + +## 1. 디자인 테제 + +이 제품의 목표는 "카카오톡 PC의 즉시성, 익숙한 효율, 낮은 진입장벽"은 유지하면서, Windows 데스크톱 환경에서 더 차분하고 세련되며 오래 써도 피로하지 않은 메시징 경험을 제공하는 것이다. + +핵심 방향은 다음과 같다. + +- 한눈에 읽히는 정보 밀도: 화면을 넓게 쓰는 PC 환경에서 정보량은 충분히 담되 답답하거나 복잡해 보이지 않아야 한다. +- 즉시 조작 가능한 구조: 클릭 한두 번 안에 이동, 검색, 파일 전송, 대화 전환, 알림 확인이 끝나야 한다. +- 차분한 고급감: 과한 장식 대신 균형 잡힌 비율, 재질감, 미세한 움직임, 명확한 위계를 통해 프리미엄 감각을 만든다. +- Windows 네이티브 감성: 타이틀 바, 창 상태, 포커스, 컨텍스트 메뉴, 단축키, 다중 창 사용 방식에서 데스크톱 소프트웨어다워야 한다. +- 장시간 사용 최적화: 업무 중 하루 종일 켜두는 앱이라는 전제 하에 대비, 타이포, 상태 표현, 알림 피로도를 설계한다. + +한 문장으로 정리하면 다음과 같다. + +`친숙한 메신저 구조 위에, 생산성과 심미성이 정교하게 올라간 Windows용 프리미엄 데스크톱 메신저` + +## 2. 데스크톱 정보구조 + +기본 정보구조는 3단 구성을 중심으로 설계한다. + +- 1단: 좌측 글로벌 내비게이션 +- 2단: 리스트 영역 +- 3단: 메인 콘텐츠 영역 + +필요 시 우측 보조 패널이 열리는 4단 구조까지 확장한다. + +상위 메뉴는 다음 정도로 제한한다. + +- 채팅 +- 친구 또는 연락처 +- 알림 +- 보관함 또는 자료함 +- 설정 + +기본 진입점은 `채팅`이다. 메신저의 중심 과업이 대화라는 점을 흐리지 않는다. 친구 목록과 기타 기능은 중요하지만 1차 목적을 방해하지 않도록 한 단계 낮은 밀도로 둔다. + +권장 구조는 다음과 같다. + +- 좌측 아이콘 바: 전역 기능 전환 +- 중앙 리스트 패널: 채팅방, 친구, 알림, 검색 결과 등 현재 선택된 전역 기능의 목록 +- 우측 메인 패널: 대화 스레드, 친구 상세, 알림 상세, 설정 화면 +- 필요 시 우측 인스펙터 패널: 참여자 목록, 첨부 파일, 링크, 검색 내역, 채팅방 정보 + +이 구조의 장점은 카카오톡 PC 사용자의 정신 모델을 크게 벗어나지 않으면서, Slack이나 Discord류 데스크톱 협업 앱처럼 "맥락 패널"을 유연하게 붙일 수 있다는 점이다. + +## 3. 셸과 내비게이션 + +### 3.1 앱 셸 원칙 + +- Windows 타이틀 바와 자연스럽게 어울리는 상단 영역을 설계한다. +- 커스텀 크롬을 쓰더라도 창 이동, 스냅, 최대화, 최소화, 시스템 메뉴 등 기본 OS 동작을 해치지 않는다. +- 상단은 과하게 비워두지 말고, 검색, 빠른 실행, 현재 상태 같은 핵심 기능을 밀도 있게 담는다. + +권장 셸 구성은 다음과 같다. + +- 상단 바: 앱명 또는 현재 컨텍스트, 통합 검색, 빠른 새 채팅, 사용자 상태/프로필, 창 컨트롤 +- 좌측 내비게이션 레일: 아이콘 중심, 선택 상태가 강하게 드러나는 구조 +- 중앙 작업 영역: 콘텐츠 중심 + +### 3.2 내비게이션 패턴 + +- 전역 전환은 좌측 레일에 고정한다. +- 각 전역 섹션 안의 세부 이동은 중앙 리스트에서 해결한다. +- 우측 패널은 탐색이 아니라 "맥락 확장" 용도로만 쓴다. +- 브레드크럼은 사용하지 않는다. 메신저에서는 깊은 계층보다 현재 컨텍스트 표시가 중요하다. + +### 3.3 검색 경험 + +검색은 데스크톱 메신저의 핵심 경쟁력이다. 상단 통합 검색은 다음을 지원해야 한다. + +- 채팅방 검색 +- 사람 검색 +- 메시지 본문 검색 +- 파일/링크 검색 +- 최근 검색 + +검색 UI는 단순 입력창이 아니라 커맨드 팔레트와 리스트 검색의 중간 형태가 좋다. 입력 즉시 그룹화된 결과가 드롭다운으로 뜨고, 방향키와 엔터로 이동 가능해야 한다. + +## 4. 리스트, 채팅, 디테일 패널 동작 + +## 4.1 리스트 패널 + +리스트는 단순 나열이 아니라 생산성 도구여야 한다. + +필수 항목 구성 +- 아바타 +- 이름 +- 마지막 메시지 미리보기 +- 시간 +- 읽지 않음 배지 +- 고정 또는 알림 해제 상태 + +동작 원칙 +- 행 높이는 너무 좁지 않게 설정하되, 기본 밀도는 "업무용으로 충분히 빽빽한 수준"을 유지한다. +- 마우스 호버 시 보조 액션이 은근히 드러난다. +- 우클릭 컨텍스트 메뉴가 풍부해야 한다. +- 드래그 앤 드롭으로 고정 순서 변경, 파일 전송, 창 분리 같은 데스크톱 행동을 적극 수용한다. + +상태 표현 +- 선택 상태는 배경색 + 얇은 강조선 + 타이포 웨이트 조합으로 표현한다. +- 읽지 않음은 색 하나에만 의존하지 말고 배지, 굵기, 프리뷰 톤까지 조합한다. +- 음소거/보관/고정 상태는 아이콘 크기를 작게 유지해 시각적 소음을 줄인다. + +### 4.2 채팅 패널 + +채팅 패널은 이 제품의 중심이다. "읽기 쉬움"과 "입력 흐름의 부드러움"이 최우선이다. + +기본 구조 +- 상단 헤더: 채팅방 이름, 참여자 수, 상태, 검색, 통화/추가 액션 +- 메시지 영역: 타임라인 +- 하단 입력 영역: 입력창, 첨부, 이모지, 전송, 보조 액션 + +권장 동작 +- 새 메시지가 들어와도 사용자가 과거 메시지를 읽는 중이면 자동 하단 점프를 강요하지 않는다. +- "새 메시지 n개" 점프 배너를 제공한다. +- 날짜 구분선, 읽음 기준선, 시스템 메시지는 메시지 버블보다 한 단계 낮은 시각 강도로 표현한다. +- 메시지 선택 모드, 멀티 선택, 복사/전달/삭제 등 PC다운 조작을 지원한다. +- 긴 대화에서 스크롤 성능과 가상화 품질을 매우 높게 가져간다. + +메시지 레이아웃 원칙 +- 내 메시지와 상대 메시지는 명확히 분리하되, 카카오톡처럼 지나치게 장난스럽지 않게 정제한다. +- 버블 최대 폭은 넓은 모니터에서도 읽기 좋은 선에서 제한한다. +- 텍스트, 이미지, 파일, 답장, 링크 프리뷰, 시스템 공지의 규칙이 일관돼야 한다. +- 연속 메시지는 아바타와 여백을 절약해 리듬감을 준다. + +입력창 원칙 +- 한 줄 입력을 기본으로 하되 여러 줄로 자연스럽게 확장된다. +- 첨부, 캡처, 파일 끌어놓기, 클립보드 이미지 붙여넣기 등 PC 특화 행동을 막지 않는다. +- 입력 보조 버튼이 너무 많아져 웹 툴바처럼 보이지 않게 한다. + +### 4.3 디테일 패널 + +우측 디테일 패널은 선택적으로 열리고 닫혀야 한다. + +용도 +- 채팅방 정보 +- 참여자 +- 공유 파일 +- 링크 +- 미디어 +- 대화 내 검색 결과 + +원칙 +- 항상 고정 노출하지 않는다. +- 메인 채팅 공간을 잠식하지 않도록 기본 너비를 절제한다. +- 탭이 많아지면 세그먼트드 컨트롤 또는 상단 탭으로 정리한다. + +## 5. 타이포그래피, 컬러, 머티리얼 + +### 5.1 타이포그래피 + +Windows 데스크톱에서는 가독성과 밀도가 핵심이다. 기본 방향은 다음과 같다. + +- 기본 UI 폰트는 Windows 환경과 잘 맞는 높은 가독성의 산세리프를 사용한다. +- 채팅 본문과 리스트 본문은 숫자와 한글, 영문 혼합 시 안정적인 폰트를 우선한다. +- 제목용 폰트와 본문용 폰트의 역할을 지나치게 벌리지 않는다. + +타이포 위계 예시 +- 앱/섹션 타이틀: 강한 존재감, 그러나 과장되지 않음 +- 리스트 제목/채팅방명: 명확한 식별성 +- 본문: 장시간 읽기에 최적화 +- 메타 정보: 시간, 상태, 보조 정보는 한 단계 낮은 대비 + +디자인 느낌은 "날카로운 스타트업 툴"보다 "매끈한 프리미엄 커뮤니케이션 앱" 쪽이 적합하다. + +### 5.2 컬러 + +카카오톡의 노란색 아이덴티티를 직접 복제하는 대신, 다음 방식이 더 좋다. + +- 기본 베이스는 뉴트럴 톤 위주 +- 액센트 컬러는 한 가지 메인 색으로 통일 +- 읽지 않음, 성공, 주의, 오류는 명확히 분리 + +권장 컬러 전략 +- 배경: 완전한 흰색보다 미세하게 따뜻하거나 차가운 오프 화이트 +- 패널 분리: 얇은 구분선보다 톤 차와 표면 재질감 중심 +- 액센트: 채팅 전송, 선택 상태, 활성 탭, 주요 CTA에 일관되게 사용 +- 경고/오류: 과포화 붉은색 남용 금지 + +트렌디함은 강한 원색이 아니라, 절제된 중립 팔레트 위에 정확한 강조색을 올리는 방식에서 나온다. + +### 5.3 머티리얼과 표면 + +웹 카드 UI처럼 모든 것을 박스로 자르지 않는다. + +권장 원칙 +- 좌우 패널은 표면 레이어 차이로 구분 +- 모달, 팝오버, 컨텍스트 메뉴는 Windows 특유의 얇은 재질감과 깊이감 사용 +- 메시지 버블은 과도하게 둥글거나 젤리처럼 보이지 않게 조절 +- 반투명, 블러, 그림자는 최소한으로 정교하게 사용 + +느낌은 "플랫 + 아주 얕은 입체감"이 적합하다. 과한 유리 효과는 피하고, 정보가 또렷하게 보이는 것을 우선한다. + +## 6. 모션 원칙 + +모션은 눈에 띄기보다 사용 맥락을 설명해야 한다. + +핵심 원칙 +- 짧고 정확한 응답 +- 방향성이 있는 전환 +- 상태 변화 설명 +- 방해하지 않는 부드러움 + +권장 모션 +- 리스트 항목 선택: 짧은 배경 전이 +- 패널 열림/닫힘: 수평 슬라이드 + 페이드 +- 새 메시지 도착: 미세한 페이드/슬라이드 +- 토스트/배너: 아래 또는 우상단에서 짧게 등장 +- 검색 결과 표시: 즉시성 중심, 과한 애니메이션 금지 + +피해야 할 것 +- 모든 클릭에 바운스 +- 과장된 스프링 +- 모바일 앱 같은 통통 튀는 반응 +- 긴 페이드로 인한 느린 인상 + +## 7. 상태 설계 + +데스크톱 메신저는 상태 수가 많다. 상태 표현은 미세하지만 분명해야 한다. + +필수 상태 +- 기본 +- 호버 +- 포커스 +- 활성 +- 선택 +- 비활성 +- 로딩 +- 동기화 중 +- 오프라인 +- 오류 +- 전송 중 +- 업로드 중 +- 읽음/안 읽음 +- 입력 중 +- 방해 금지 + +상태 표현 원칙 +- 색만으로 상태를 구분하지 않는다. +- 리스트와 채팅, 버튼과 입력창 전반에서 상태 언어를 통일한다. +- 로딩은 스켈레톤과 점진 표시를 기본으로 하고, 의미 없는 무한 스피너 남용을 피한다. + +## 8. 온보딩 + +온보딩은 "설명"보다 "즉시 사용"을 목표로 설계한다. + +권장 흐름 +1. 첫 실행 +2. 로그인 또는 계정 생성 +3. 프로필 기본 설정 +4. 연락처/친구 연결 또는 건너뛰기 +5. 첫 채팅 시작 유도 +6. 알림, 시작 프로그램, 트레이 최소화 여부 등 PC 친화 옵션 제안 + +온보딩 원칙 +- 초기 단계 수를 최소화한다. +- 권한 요청은 필요한 순간에만 한다. +- 데스크톱 메신저답게 "항상 켜두는 앱" 설정을 자연스럽게 안내한다. +- 빈 상태 화면이 첫 대화를 열도록 적극적으로 돕는다. + +빈 상태 화면에서 추천할 행동 +- 새 대화 시작 +- 연락처 가져오기 +- 테스트 채팅방 입장 +- 파일을 끌어 대화 시작하기 + +## 9. 접근성 + +이 프로젝트는 트렌디함보다 완성도를 우선해야 한다. 접근성은 부가 기능이 아니라 품질 기준이다. + +필수 기준 +- 키보드만으로 주요 흐름 수행 가능 +- 포커스 링 명확 +- 충분한 명도 대비 +- 축소/확대 환경에서 레이아웃 붕괴 방지 +- 스크린 리더용 의미 구조 설계 +- 모션 축소 옵션 제공 + +특히 중요한 항목 +- 채팅 리스트와 메시지 타임라인의 키보드 탐색 +- 읽지 않음/전송 실패/입력 중 상태의 비색상 표현 +- 작은 배지, 시간, 상태 아이콘의 저시력 대응 +- 한글/영문/숫자 혼합 가독성 + +## 10. 카카오톡 PC에서 유지할 것 + +완전히 새롭게 만들기보다, 사용자가 무의식적으로 기대하는 패턴은 유지하는 편이 좋다. + +- 좌측 중심의 익숙한 섹션 전환 구조 +- 채팅 리스트 우선 진입 +- 읽지 않은 채팅을 빠르게 훑는 흐름 +- PC다운 우클릭 중심 조작 +- 가벼운 창 크기 조절과 빠른 실행감 +- 과도한 학습 없이 바로 채팅 가능한 구조 + +즉, 사용자는 "낯설지 않다"고 느껴야 하고, 동시에 "왜 이게 더 좋지?"라고 느껴야 한다. + +## 11. 현대화할 것 + +다음 요소는 확실히 현대화하는 것이 좋다. + +- 더 정교한 정보 밀도와 위계 +- 우측 맥락 패널의 도입 +- 검색을 단순 필터가 아닌 핵심 기능으로 승격 +- 메시지 타입별 시각 시스템 정교화 +- 반응성 높은 다중 상태 설계 +- 디스플레이 스케일링과 고해상도 환경 대응 +- 다크 모드가 아니라, 먼저 "좋은 라이트 모드" 완성 후 테마 확장 +- 알림, 배지, 토스트를 세련되게 통합 + +추가로 현대화할 가치가 높은 부분 +- 멀티 윈도우 또는 팝아웃 채팅 +- 드래그 앤 드롭 UX +- 최근 파일/링크/미디어 탐색 +- 검색 기반 이동 +- 키보드 단축키 체계 + +## 12. 피해야 할 안티패턴 + +- 웹 메신저를 데스크톱 창 안에 얹은 듯한 넓은 여백과 느린 반응 +- 모바일 UI를 단순 확대해 붙인 큰 버튼과 과도한 둥근 모서리 +- 카드 남용으로 인한 조각난 정보 구조 +- 색과 그림자 과다 사용 +- 기능은 많은데 기본 채팅 흐름이 무거워지는 구조 +- 입력창 툴바에 기능을 끝없이 누적하는 방식 +- 알림, 배지, 강조색이 동시에 소리치는 시각 언어 +- 다크 모드에서만 그럴듯하고 라이트 모드가 약한 설계 +- 윈도우 스냅, 다중 모니터, DPI 스케일링을 고려하지 않은 레이아웃 +- 상태 피드백이 늦어 사용자가 불안해지는 인터랙션 + +## 13. 최종 비주얼 톤 제안 + +가장 적합한 톤은 다음 세 가지 키워드로 정리된다. + +- 정제됨 +- 민첩함 +- 오래 써도 질리지 않음 + +무드는 "밝은 생산성 툴"과 "프리미엄 개인 메신저"의 중간 지점이 좋다. 지나치게 기업 협업 도구처럼 딱딱하지도 않고, 반대로 캐주얼 메신저처럼 장난스럽지도 않아야 한다. + +비주얼 방향을 한 줄로 요약하면 다음과 같다. + +`카카오톡 PC의 익숙한 작업 흐름을 유지하되, Windows 11 시대의 고급 생산성 앱 수준으로 정밀하게 다듬은 메신저` + +## 14. 디자인 의사결정 체크리스트 + +새 화면이나 컴포넌트를 만들 때마다 다음 질문으로 검증한다. + +- 이 요소는 채팅이라는 핵심 과업을 더 빠르게 만드는가 +- Windows 데스크톱에서 자연스러운가 +- 장시간 사용 시 피로를 줄이는가 +- 색 없이도 상태를 이해할 수 있는가 +- 마우스와 키보드 모두에서 효율적인가 +- 익숙함을 해치지 않으면서도 더 세련된가 +- 웹 앱처럼 보이지 않는가 + +이 체크리스트를 통과하지 못하면 화려해 보여도 채택하지 않는다. diff --git a/docs/archive/windows-messenger-planning/README.md b/docs/archive/windows-messenger-planning/README.md new file mode 100644 index 0000000..083f700 --- /dev/null +++ b/docs/archive/windows-messenger-planning/README.md @@ -0,0 +1,11 @@ +# Windows Messenger Planning Set + +이 폴더는 Windows PC 기준 개인 사이드 프로젝트 메신저의 기획 문서 세트를 모아두는 공간이다. + +문서 목록 +- `01-visual-interaction-direction.md`: 카카오톡 PC 사용성을 참고하되 더 현대적인 Windows 데스크톱 메신저로 재해석한 시각/인터랙션 설계 방향 + +문서 작성 원칙 +- 웹 앱을 데스크톱 셸에 억지로 넣은 느낌이 아니라, Windows 데스크톱 소프트웨어다운 밀도와 반응성을 우선한다. +- 익숙함과 차별화를 동시에 가져간다. 학습 비용은 낮추고, 완성도는 명확하게 높인다. +- 설계 문서는 구현 이전의 의사결정 기준서 역할을 하며, 추후 제품/기술/브랜드 문서와 연결된다. diff --git a/docs/assets/latest/README.md b/docs/assets/latest/README.md new file mode 100644 index 0000000..4538802 --- /dev/null +++ b/docs/assets/latest/README.md @@ -0,0 +1,14 @@ +# Latest Screenshots + +이 디렉터리는 원격 저장소에서도 바로 확인할 수 있는 `최신 기준 제품 스크린샷`을 보관합니다. + +규칙: + +- README는 이 경로의 이미지를 직접 참조합니다. +- 새 릴리즈나 큰 UI 변경이 있으면 이 폴더의 스크린샷도 함께 갱신합니다. +- 릴리즈 번들용 스크린샷과 별개로, 저장소 안의 최신 기준 화면을 유지합니다. +- 모바일 웹 스크린샷은 `scripts/ci/capture-vstalk-web-screenshots.cjs`로 다시 생성할 수 있습니다. +- 현재 포함: + - Windows 데스크톱 셸 + - Windows 온보딩/대화 화면 + - `vstalk` 모바일 웹 온보딩/목록/검색/보관/대화 화면 diff --git a/docs/assets/latest/conversation.png b/docs/assets/latest/conversation.png new file mode 100644 index 0000000000000000000000000000000000000000..d163f5ee7a5aef7b4b9d4d63d60ced006002a858 GIT binary patch literal 58315 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 SGWqgVM4Q_N4RG4LW#-@dUVIBMgATKi})6r9E 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 huO) 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ⅇ7z~BNJG3Z2_o}?=mptJ-DLJP(wjU-~oemK7z;0 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 W0OUc%EXa%i0K(GlW~GJ4um1!aH)&@8 literal 0 HcmV?d00001 diff --git a/docs/assets/latest/onboarding.png b/docs/assets/latest/onboarding.png new file mode 100644 index 0000000000000000000000000000000000000000..8d8323aa294961f1332ae3bd22f543e2e11b3efe GIT binary patch literal 54324 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⪻k=f%(1=% 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�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%0AE~_t=BRS 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 JwW4*{e*x8gzexZ9 literal 0 HcmV?d00001 diff --git a/docs/assets/latest/vstalk-web-saved.png b/docs/assets/latest/vstalk-web-saved.png new file mode 100644 index 0000000000000000000000000000000000000000..1ddba0ae846eaec853f6dd05e5c0bafdf0b089d2 GIT binary patch literal 67950 zcmd?QRa9I})Hc|090I|Eg~o%s2X_eWba1C}hb9CG?h-V(LvU{h5Hz^EyF+7*{Y~EQ zo9nrmwPw~_HJnrDRMp;3ZF}|!`K%~~jzWY2007Wsq{USLfEQ>0!0TkB=ZGtLV}F+b z04#uvxQLp2+QB07Cmi!7;gdIPEH8e)G(z@&EyAmY!GKbt(NdzJi>YIKXcc8!e`Pnn z2yOP6KeD?zx`j4BE*{#|_gt2jmDO3<)>;XMsWZHL7ai(HE)q%pRs1L8O`D8O#bZYA5kmoePnu>r#~?$ zfKQir{GwkXepUdY*oYs$7fAozB>(@_O-nnww-VOY{3W#sXqbT=%`ZA>y?uPXmxxLu zw0WIjWowHQu!R>Qofg<#VTb&;C;0UA)KD7)1~ae8>M;RBvT2bx{)XDx+QRlw&98QK63rbjhWp$Ava!2~Suh_%itv?SRpIJa~qZ)_F9;YAC-l z-@PdW1MqYb?Zzm^kPx{9E;JBP72WTx@4|97LR?e%S0YT z^HN*4A_G^ahsN_44#^ICts!a?3T~yDq+=JBC8)#i2;Ijt)9C4$D7vWp1pojvWKYXx z?^)3bhZ+Kr^_cNuDIf9mxk6>xrZ^6Q5&ytG=O4bjRs6h0!l_Vcbo2-g7VLSC(+g8Q ziog99U$Spf>W0P^bJVGtGC5dAmuc3}wmO&%yiUbh?-|nRd=RjF;gD6>1&Yc_yu*5`DH!l!4tR~i5~8lF;}7qwAG78d5d9Z=ji zaP-1pR4JUyNn4JZpQY&IYO44#!#CNiJf5&17jndV|Ff2A4oxbp^W-r2@iy4lXE#pG z;>ZQnr6VPix^(w~7iGEc_9S{~$u>j+3g=tD^p23xn(9j%Y^>5_#5Z3n3o7!>Bk6~#q0V_*H z#5Yq@8XgnLsh350%?fTyCRRVGSUq_ZBRv%r=oJ*k)Mw^(MJO2^#LyPOx?EOPd$(Gi z^%Thi@|r5xB^s2J&j1aqDfAn>NS3VKF5OX>fJKkS98|&g9ziq(4hB>qi7|7;a|>(~ zecs*#CF)krIgHIHfnKCYpoW_^-&4h`v~!+A1*QA*Q{ss+=FCRbuyma~*NI#Uq08H5 zu;-*JCh=pt@Z&G)_sS@McB;PP?M)A&kks=SJT%o2Np2lm2Q$__4;saxY8d&V;BbEh z&M-Isy35$&#cI~0uJa&a`AyZJ8#8=YgnbwFw_d#4!Mwn&rcU~g2X+;lrBd!>zBy>y#nQa)T~rVC^ea^*!cKn|J2#gbf-XP{l*!!O*nS;Q1#TO}_JqzasJ`vj zGsZ^ZBSnA>--C_9ExjOL(=jY{+)U2Vkt)RfHSZy>hiu8$wF#Y~R>hs^Z)#12JB8P~ z)wO%RLRbKRWQzCTMKTjpdMyZ&u9j3+YFX9BqoNv*e|c8@u!%#_ON_muMP&^V0)%3A+A_uE?fP zb{nb0`a~oNe^pi0<Y{R>-Zy7ZAJ7LNub9V;1RK&f zZyto%zDoRKbW`!@3E$}ZyTt8vdw8Xo-hZ<#{J6EfE!DPAX>9J~#O@$=gddxdLexi& zcD70?$&z*h-}fCV8nnt;%MnYf0k9+7KT5{-G4%FfY;0_3IgRTOo}0T*Oq_~>8a9+E z4Ca{XTNZZNgT25MID_w3jChXqkbxuNJ@?^UFSNmtcq& zE2qoQpD^4mK?-0aiVF-zoQ;S~{80M`1|ILu_0JBKb0k~gdvb5zzSRWR+04d%|9+|@ zbOYk#9D`%k1i=QmX+q}z1z&#F?pKRf8^p4CXSLH0hb$C*cXxL!trdSe3a68B>I0); zf;o|G>jlCgT+v@u|NJQ#oq7#85fmK|eS=Aw2!7n4i4vERx~MVL(723K5;CoHm%ai8 z*yR*zgI}cnjPoLk+LTBKJi`CUU2w2FT!Mf@U`T_ z3j``M+uJ|gNIF%ITuI$OTtQM;jq&cUH{uSv1RFtAG9@GvD7F8Cg2Ap3oDJevuL6A_ zYOa@bHI&E|Z;q61q-maywG}L?s9MU${LLR`UuAW5DhLf5o6*JIq@ZwzHR7<+(e>fbC?cFTU_?py zZWVqwFKlaNHCGRQ9Ltm28k9RUT}R-+wRAc$u`Z>7(T!xWy9!EzybIqiXuUm}Ct2)A zJo?2VFu;Nga#x9O5Zy05EOfh1lXx|M*$G3B@Zhma4qAguI!tO5f@vbdO@bWrOu6D65>s7ATvCq%Eyuc-c2Kxtn zH(TFf_o{yUMY>I(QYx_X*)kCDDv&gEQCS% zxstY^o!a#H;6>)S^K5Oa9YntRVCLKGQrYDKcOz zrLX&O0~STY!#vt5)A#dhd12^dkXJt4S^89qntu)bX@)Ym9O9Ljhvz@5stnF&WL4!h z(XqBi!NO*K4)eLUh#uGy|7rNy6<{ESU1oOZ{aAe&CfjY3pn@tWURtn_tju!`QLh^-7en-!j*A?{yu16ye`laHJR+IjOia!IKlL?#`=;(<-O+nNh5hyCJU%q_#DmWY&}9MMVPXI~G=6zF+6VFk*HpsxvK*Ec@N-d|}cjqsa7%qVHnFJI=u( zY2w{v=W9Lbj*eGyu{5vs6C8C?_DW#*7lAk0v)c!lm}$T6E;+07FvJFqy;$t$Z-Y^Y zi>ux2`h^H$qoa9v`Sc2!nR+WK+FmDJ@KQNSV>38Ho8#XLbQ_K|!P3iY753&}g4pu@YAGV(oMGg1bi4}x;d8Z)Q{@VQ z@;9e|3x@#zl%e04dFvQkTWZzwMZo@W(TjF#nE&oRK>&M z<)^DU(bD(hXcD3_%ob>Z?&`sB$Nq|)!6b}h?OKHmWz-GrBqcQ}p@y)7cp=x6!OSrwI}FF&G>rWqUOt2 zumkIRtTlG4zwxv6@{N~4hdO)1hw~32K^*r2_<~-BjvrQ;pp}PuVmDcCOO{%0w;jnp zmwaQq{qyt7nyG)|junjTcj~>8VR)X@U%vqB|~M9W%2M^Wkv0$v|zIw*FtW zlCQ!QkVqbEMAnrh;iqxtD7LEa9oEejTbbyR@%&@$np*Q$(_tu3f%V0rF0TyE66>nkBM zo4z`mNIYv@ zJQJ|JVA~^hgEA~vy*$3DKkTAq=7YVON=#4!X##SmH0U_c#=|)19anwYB^plZ_sL7Y<%)Y?AO#yURt)i&hE*)|$>MBdGEv@OA5f!#kTFvVti!W4*o8)F_Rvn=(sPi;!c+8pxE#mXlI`EUv zWZlg6rA&oNZ7&!TwC9Bei{IX|aAgvv*X$+El4`EM0@zbRq0sD^u5*7xs_V%EV;|r< z{+>2{_>B~Im^&Sb z{xbt@A^iZ#W3D)w$2dz!LPkEc4TCQX8%*+NNUPWs_FO{%ej?e^3*FuMiFoh~ZP@8) z08<9+)5d-UyJ`Tt7`8R4j7E@h!!b;wCLGu%{hm9Q!g69JupTUJg0 z#W^P8(cPmYn7=+Rr1xcKhBZ6yDw8_&E>CWHCZCv^#nqKwby}AqL}>TsKon;eb;-F1 z;XK<}vZl3#h077TQI;tql~^DX#Df1Ijc2C}I2Xkpl;qGFNfYl}!AderD{RybWqsd+ z;Q|ISNpCkuIo)oA%KO=Q|E=A|K}oK%T93E372i{AJB=EagR_^_Dvfl_%F$dx2RHA^ zmT^+zBR-D{n1_nzgf&8ssIOaze^SLD>R6LigcoeCp zG?UVByb6Jn#;aTaKME0Idqj;Yk|~K>~S1@EM+Jb?ia?mg|s7}PNVc(e|iTTQu^cknw z0^+C(%)vygrK!hF;Tmm~F$IhoT|Ofui45;3C-ru6`+&`r>cd2Q-DkYEzNgQA^3%16 za!?eh$tr38#0LPNQtk%7v~oucVJXB|E64C@wiFzjbf?^X=0(G8?wYOp;Uz~A~6e}QzzHL)|QGX{3xSuf*b_d zwToK$e*M;ZGw<&z&xuZZe^<|Y&r~K@EaIC&Qb=F z7-4%FAc%)arxdfrXjom0E|J$R>)$JZQoUP_HZ7?=mgqQ?(%p`7!}Z5!#3nYil>JZRL!$m8GSng+*+M z9tUzb5iZMGYoZhs{4s*OW;Y@TsH0NFsJ*ag?+3JiKTDL86#&xW0Tv5>mfVEEe>& zWx}Uq!k)5rafHElyC2CG$N3>Vrq#Yo7Fm!PhT9=7{uEV)8rWOv5-R{X@_WfRN=N>R zr$thK&`FD~cqrG%`s9S*d)lwwp;LX_XSiC)XXJ&69l?zDGx+uNK^w9O;arOij>C8X zPw5^xQ--GGOT{;cKmT7(5UnIq6y-_el9iQ}*>crA%7SZ=7Wl%QlJE?p;3%oEQZaX0u3<8nD8|>h+#Obi}aQ_0cLQKHl?^{J(Wi)3csJ8 zw#P_<;*Es{{}~fE{(46h@w^T*KNzq=5P^y!arI{8`CQWIgcz}2$}NqLA7Q&9e?6^*40 zHLI353Xx}?fvthQe;SZXlMBerr-o)_#YjiUqV-f#F5L${jcTM}Uu13a>I(TcOz(GZ zFk)C%rE=5J95uUhmXM0G`9>e^?$KyEQ1NRu;smaIKu}Gb-1Qt>T%*MAxA*<;{dbv0 zNhnp=8rVcePySN{2_<2y_$d8Vi=j4IQhIpsQL>&0q9ES%ahrAGzlr+4>xb*=>N11u z$m>S8Py*$m7?XmRap(xOcI@H|{r=5lg`je3Yimb~Wvs1JMOP2;1_EZQ!s;3Owcxcs zZ9pqe(rWk_gDfM!JTgUvh@`o7Tn#dcz8`T7W1h0O|I;r%$8(Ah4f44+_BTzVM)+qN z2L7_aZVb|bhx_1p=vw(dn)!6;{noH^uDJ|kMQ}2h*NjtYhx7iwT zM%7+^{O3fpCGz2?J)km<#!C6(uQhI?p{bc6s>vDB!fR&iiwgZ`ueTwZGz}18SF* zy|Ybn7>W<+2o!{P=F5rh|E5NIH?GR; zJsJ}c^!v*{=~_FgWCE4ryMy`ZaFEpB7~D-O=iCLJ!PVQm9mZ*z-CE9*GOf=S=>E4B zu*~*nX(;~@_GcPeMD-0g{q;3VRJQ8yYeBL>^aSkp84d+SRGG2baXQMz@MUk$U-D4G=)2C^ z>8K;;<(+u`X#>bfW`Tuv-sVIBf0&);N4s}~EH@Uf-^Hf6a@j~qFfL=6w)yZr{y7kn zFg58#zy1%TG`Q?@#?J^{Yb9ugC;GTwaHJXJxC_|m}2y(F#6`%4Ywq9Oz*pElP!` zK??-LiK^(PIv>7&IMo|OukF61t^p|cReV$M=FFMjjaPGWwABD2pC1J=E}y_*TcT0{Q_7y zNgbSCOkVRXbk*myPb@6Rzpiv{_2xOtx(FK|3S$3XDHR0f#?Z3!pvgd?>6&)L@>DWH zY0qjXBLB`3nEkKz`x0^ukAIEj$#1Yc3oPJ-hdak=1lrA$dFF@n797}F{g(P zRgqIvPEdx5ic>+v8`jNB@|1ajs@Q-ngtI%LRcy-ble0&IFuw6W+mUF7sIH2V~Vb6`=#`wb*3#_ z`>IWxk2q#-?WAn3m}<1ItaxmND?kMG{ zy^Y76b+R)Rhj(+CJYhN=Mg798R`y$?)>JSo^L!|%8g1)8ux?+F$5i}0n{?I+%wO_Q zZSWpgfax4DLr><{1&I zT-X2Hmwy`HmsAafo$W-tQDhwI2++emH^}2GpRS*2c}iaYcKOj`vxxWGPB;+vtBy{t z2_@N(AMdFRT22<LoB!;lF;SG=l5&;>vsVpq{_q%A5 z!9m7si;L4!<)U~K`!onw&;nM*Q(S{j!ClYG4;)1;Ms#NIPX|D5PZZvc>)xk5ST`HuOV0p*pogM*_J!J=UT{g+Y&o-j1GkW_oo{JO4vdgB7!3Ag#S7Sao5osLTRS*4H!15DbTsze5i1nQ8qv!L8`e5_zRHmPx^490FEb~cx^uSNeiDrQqBYVm(mG8m84 z0pY3})Qq}qmeVPsL{C!rBHmym{b39^F-A)tY0Q{N?Ky}SfCmQ0XlPjQoLpWKc}tlQ zJue+A9c81#x`x9?Vv&6u*F68tkv1$L78}0hr<@ai_A-cr@O!oomzAzibm!M3g?m_w$QWlbA-+=Xrj9R9Wd%73>h}h-}w^n;1p33_yK0~Gwy5z zHGe|u3?r_0;QtfWU~nVJknB*U84T>cVf3GA!mW3;0$L@|oHg!Is1)yzb1Hdwjmv*p zAbWyA`!B}k39 znw|_BuGto0fDm}7*yZ$eHSO*dqCEy*sa17mKWFk3^Wm6msz&&UjS~Jz53nJaoig9ae(FJQp*lUb8>7EV@mrWY#*} zR?14&pCqcQJ|{xKY^^+OAxL&#&Z!sYW1wnO%>=sK!Dh$R9jRnkcF#ktHjb@x)|{=T zpdlI(h&Qwcp18pGJY$dv*tr_WZi2vtrG>>R*PP!OO-y*Wfq{P?{_xKqHEK8!gD(G& zJ?sm6EOM`BPq$s~dfwvaS7hqe!G-IpR3`S?Y#!Iqt)6znB;KOuk@f2rIX6w)uTX=D zgz+QKovGv@L8Gpe(qSQ94F>GW{TB%_s9(DP@P)8 zdY)f9`JB+iC&?7Kp*Wy(E-sAG=ezh` zm)@1;J&#Lo|7j|MLk$mvZ7Hd;|6DKoJ9{=oU;Ncx)bvMP*YPTKx5ssB)t>rSBH%~S zGL@31+}ypt)1+EH!@-W?DfbIu$51%rk|X!=A3A@Rs zu%=8>g=3y%f#%zmKWAhrD@q2Sa@C&(Rk3+MWi&|2Y>(r*cC$M@S_LUA@dd7qN-(Ks zp2vH_*U1ij508NJehm%Hkc&9N@U-dZyOaXZm!oS5QzV(^?Q4*eWdW6c#(@rryfCq8Nlbh*Ib&GI6#w6 zMjYN1v#yi_X4fqpBxUiTbX>vWbN$ej+=)%PujtzO3$f>jMlGk-=q_Sd;ANnk^J&vi zj8ytbyHqggPF-VB(=U0js@WQE*{sQk@+kM+srv;PBH9Kt@KiEwSG&2&b}l3O2juc2 z90GqovQ4APq}i=jEcurG{lbPi7pN@Aq;3trJY0TUE}6%?;1~hnwyi{F3SEV9o)&&Y zV61_@k_ocRuY0z?JLs33P(M7$YkM&#WdD1M%LRsxngiZ(iQD@&j4&P6%a?V~5Z>bv zYfSG{&CAv1C?zRHgvRZ871Xtxg_jYHU#Q8y&W?0r{q-3Vd910?`w7TuPaOW=!KxFWWe!h=7;`_=$XRQBdFKl+3qP{x<-l7tLiLykQVF^AKZ31 z#44yD+w*YTSdVW95%@}p*!7C6j=O4zfvogGgQaCX^AGF|$3SB9!|2r&r+Q)MUp>6P z273&=EiS?Y7y5=CI`LD<4v_d-D9&{~cHg?fA+7Ma^;1o3te!v`i%y(;W*veaCONON z@A(zk#vVaEI352ai=Fv%CczCXZSh%Z@40>fnTmNi)HRi|RAX@LnsPET6h@5=aryC` z`yK8qKct$Tv;V1oVrWwBZdZ=XY{Km!k?MA_%}kE_T@1r<8^b0a868bT6!HhA0c{Ey z5}zJsUu-b>i>%O;@i7UT%29t__~uliOo zZsIn0264^*xB3B_M^Pp<1}eX9GXY)QWhCTeOS|dW`$sINE~8K zX@lUk;(`o)iG|eZJUJJbaE{a)Y&TcmrPfuOn0DAz%lbqY1{XcY*jN=8O z(EHyv0{?fWK=he&a-Nzwy%F;E)gv44rCf z@{B8bR)lE0Tk-0!5(AH{N$aPFP*d0Pk<-#f8@}s$749vay_r2X$LDFa!HgS@IZz2L zWgbj0SmH47RDM#SfE=O@X3cQlmQY8+i8}avSXW{ck=8{0B)`}h6{+bJmyG|vlocEE z83g7cu-diEuD_3e==TGF1LXpqL~PGiVqsgu!J$pGYpe4C8M#pr!4XM{+S&>d&dREo z=KZ#@`^oLC&Go6whyeEC4WifnL4_q$*{Zf}Y*`O%XK7bnT%0v%&p{vamW{130B@>) zfFU*IrDtk4B_!{kvS#>}Ug~o<^%*tVu#Mxy8z<%v{hlqI)>}leJ4Qu`U|M(}|3aUUH)(Bc%speooSc$t;zOu1h=l3P z?-5Xopws{Wn07m@We1Q5Xpu%fwAK<0Af1lk@Yb)FS^Dq$C6t;*kEHoe+ew_D&H+#*r}O zVjTexK)c_yqe`@eg#}P?wM7=Yl9TocR({V;YX`|TYGh;M;Gf6qxIQQ#=aD0>173Wn zSfLI^p|Q7;K9hVpCw^aJzXfau=4$xMK0S!${~jnlT8*f&=n~%_Y;H=V{Vc03t4*k> zoAWJ)h47?0|Fj*0w6wlk>au>R@fHytp9mNrJ|5sz$YZbxtMkU8uI?W^=I)-ph;RAfS4;4_v=A(pzl{6f2-cRzar31% za;$7w-@UxYMyh~t0w}cqvxWxauf3DgRf?}-&At(I>!7Fyvy!hg0mN+&fzw9{U1x}% zQhVDYZJ8<^OVR98|8?(0yWV8ctdsNets664r>qHy5FSiWdgU(UA(06|my5MpNQBc} zZA||#g)t*hb&snVJ=cK{rO^@OP?oQ*KF^` z*Tr4*qr=+^8j$aGxw%`yMZ?$6`pI&_WA$xqyQfLZH=7a0@5M7wZy&b%G9Qc~(=?;h z!mtkOHYg(=FA_k21mpo5xAlk!{dMD3rZh%Qh5mw@zo;&V%km>JI-%V}zbj|cITR}I zR830~8FlqJw-vqsS#CYsbP@z(osX0Y?#pD_Q-rXa>HW;ZU$SSa5OgoWENiXTE6Ks<~Ke&5;*LX#$;AMY1NjDIE+8>o~d+TJ>kkVRJxvB>#F+vb}2xp$& z&#P<3krHmEn)8JUWeSuxRme%SxFHTNnBoPWi!W(uXIF3_m-6xOd{ishD(`=p{l`SP za8mO)%A)uqV+n`O_cm8OlZLu!(L{@)at;*O+#oGyRtmrkh4d&jlZ*3!h%LQj)gm@Tq9KfG`-9!{79RW-d~jk088abBevQcV=+0qVy*D(}HEs zHT*;5kcbSY_3&gYBl>~GN7D?(Q9=}_=ukjAr}hvT+*#Mvw5N%PgN!cZi)AVMNYvR_ zHE&PNxJH>i7U9UIh(spzYIjyLSjnP{=WYsw>2^y^+~{nqlh#yVjSBAN|8-?=5HWH? z(ZpM#k-x*|%T)1bwO<#G!Z0eYUF)kok#N7tpm^{AleX+xqUERz8&B+xa=q*9+ACFB z=C|TE;4-=i(#4tR@@Y_&wu+k~jv<~CFv*}GBTI=($@^xN^CwpXue;&oPt<)%GkM@k zsF2$<@taMwx4!Fw4A^;Y&-PBkI(WH~!a0^cYmH=l5ls}nDg728o=ZB#;CpE^S3a3) zD`m)|`DF#XTd4YjjjPp1tsER!0XZLUbLA*kKlY+Nywr4j^&GGgYhiTlUBE~|*F!ZZ zmqAoyT~YcyajrjBm9U3hZZPOTpMP?HipsJ=FB|uPV(xQ}%)$P!VSLWLm**8@N+^cy zY=Utvf7a)~T~TdlRcXHsmH)%e_8T>~@?@YCLuFk}O$Xj)_$HO45dEhIF$N;1tA1pe zEF%23A2jeZIEhd%?Ti!l@!il}F9(&#dJCP$WUa-9%jjk50>rJQ%X!Jh00{CY*f`a^ zDuMPjNvvJVV+ir)JiZX^?e)*HAM&_P^Ehpb1a_Ej?|9kHw78<{0%rZ?TZQWXI?odX zgBj!}c>^a8F_oP(iH3KS50seW+l03(d%#B%_p~NQVo`tZS5}$xTtB(&Re*e5gAR)n z2q8YO8|J~6h>B52ezjC&k{o4dL1~kdgIW?FW&~%`wM3Kj;zLy3_QY;f0fgH@U9<;J zZ{}|ezqzXq+tP>CSMbkyI-5VsN~KVOjPG1zxGU1~;wXtgHh)hXi63IT9yR@0JiFkh~;<(h=~@l4({^~0o%(s|TpKcg#qs0jD_q!d$_JyN(n3<5j(FC->Ks&8T$F_61YPV2 z&9>l;l3`L|$2)nMMRiW6K!}!FKe|)C;^!-0Uc-qP)Tw3eFndiST>KPAF`M60-qn+( z+*&H4Ho}SHj9L(-(%&62Bq1Az;>L!GF@Z}+8A8m8NAyDx3i1lQvyT0Gh_1H#@R6ZE zz)`i6$rzQdwB7n-a*C!LO@;}}wxZa!R&ii=c|w^f2Oit9G6OMJiwo+dhXLB;y;lZA zH*Frmwjt~2gxheKZa=73Zn*cLsi^TI&G@Q4hpKjlw-n(Z(|TX6Ju5M2&c+kA1Ug70 z1#bA9?e_@XxD2NTfAoGxPdeHc&|&;cj4cfzXf>rBpeq3pJLbu8gEZW;eEi73z4v>C zwr%o_lP{zZVVam+JXTT0(52&P-j(d6tEFn?yt)pD$?=^@<^o}_Fs*!!h6+jtDqh&E zMIo!^yZ1{v&RcNY5+8&Cz+fU|DT4O;(tMjMdHNktxal z53Txlq!P8uv=#A`Ke+*0705>dX%Wi7R_|^irX+J_MZ4$1K#Au)<9Qid#a$BB&U&LV zYl#aVfgWC7D}1DNMzQL}K)RrOs+hVM7uXgB%$2Z$>b{dosJ3E@j;-{0LH@m~+#J|) zeXcJ@YW&=hDW0%cL7)Hw85vV4JTs1shL=iUYijCo+!+J ztVcj5DD7LOb9r2m`9jlbkRrrKep_h1W&iyd4j`OE@#@>3KHPaWdMh@rHf16~DkhzR z%5C(LJPlBM66oXL+XAHf-)r$DAKod5#88Bup;+%O*=A1XcLm?NDbkDDicC%4xWE!s z@F#~u61(>N;{xljl{en$GgHDOej5=NYpp_brFh zg~#WN0f(F^hN9SwED9O;0`+Dtg#(2XM6y7!Uv!HmGvR*c%1I?;f`XqrfU5P+tE_`+ z%?wCUG+NqFeDW3#Q2(0TcFXwoO-371>RrzZFS5F_mhL|`-s={!bEy!Y&A~DdsE90Z z#2q#g7nM&HxqH{J$6Vre+~FMySb6^3*=ZV##_xIk+00VG@N%`|;E1^JLoEJaCds2IzBmi>BOA%GMNIbl(jT%6nk-9+crL%PK}icWu^*aUa<=bk28?R z{Q2_5yt%=b-R0M_*066CZHQQ&HaT77e`^6s6BMiKJzqGn5lKimwUw{V#QU(U(wdrI zJ2r+65rJGz3gPMR-{KPH?fc-?0Ty93x9i1np|btfAN5f=WL+#~x(ZJo^07ywTZQ$R z6DqnFS%mGJE28obt?Jei)Hj-Iwnq^WFX-LJMYou#%WA7qmANpJ2>7|AzHX=1enftv~nI z%Q$~{)tl0HGtRp2b6jaz550I3<`Xr!4ZUe+&+MfGvK|K4f5d!i=YC!Q8ed+liXv-u zYjHgvu2}rWZ|^N?g=x6g5tzj5fhVnz9_=V*1;LLYsotsA#k;Tv&ED>B3Eef8C9pW$ zGXz887KXRCnBTiUg2=qjHoxX>o=oUkl{(V3INY9IaBMOusdpUuUUoM_H)8~(`gpfm?1Y^ z`D7UR)oPPp3zEuO*$kA*3s~F=CeSKZbj!JF~mcAoC zURUhV+%S7bP)@W(m2y$Xow-#N7mQI@-UMV%*~u+hl{LMdXk5j}s19Ie^2R7z>3 zEfOBo%-&sCRdU;!@x=I3E}e>F#iV0!)`!H$QSf4atTo` zUSl^q|B-W}YFjdJYD%LddrmKSMYdiH>HXTjuoDbc3)$x$X?mLz+3diG%)n8FgI(dggE5l#_lH(hbAvME zx>d8vUu(>QLk=9x=}=8=Ey%3mEi8r&=7|H@cxfY-5OGr}N`=-vT+S-ooVj(EdBdZw z5bU7fNRZt1YDsEuPyPftgHaQg=d1jM+yBGdTL;C}MSZ%waeYeP7i~)t#zacW%uL|4`NE^f|ltUT2@Z*IK`4 zO$2eK4>>=3thN-deX{~0pIa0;^%FYpC~%d{#q@fW6-n&fg6!}Tl4MQgn9@WOQMuV2 z=6R#J|FHP5+|z8hqaQEvEf^JHJ^6Ueb?`0aLdT!7`C`>{qERm+)u5-9 zPw?h|P8f0)wCi!n$A#VCaJwGDEnqXsA^t4p@l=xc&B{E6e5-cLQb%9iL0S5Kh2#6< zO58p*SZI8%IvtI&>HZ+Crbh^zRW;-KQuQn;W9o40Eo$)@+NNPF1uvED%brB#h}&rN zNBG(L|wiBatv9V{*VI`y4Xf%*Y;NI}bn!fLL zdux;2+WJ5%N87_&1AS%&?YGFUnIXAi^~_W9WUk^XpO-`1{ACVfVV%B|t;?GkSDMwA z8YyMiKx9d_=IvI*_TM={r*o%fwh)~Ql%w4>+*(q-KlS=@7PV>}4hm~N9iP1S*O^6I zVypeNrtD0OO(2+#744Fl`-$&*h9W32FUgOJ5&4|iWde;@R6B8um z^~pM`U^Hg9VR2Dax@4}&C`++@*omAl#_sJDU~osETJ_#VtcE-MgX;@xTDzl5y0+zUd*i9n;ElZAP>?6{F76f_C~t4bvL(5 zbT#~!H$4NR97JdF2eYbLqh9ATG#NT>d(*^e!LO5&N$l2peQAVdTnlmT%}lm63nOv}Uo&4KO8aD^GTZzpt#3v*!n^@x8qa2rv}(Oh+4d0|+v@lW!23Sr4gBO!u!T=`OYo6?^3HOuCzfNYc>_oJRP&Eou@xV}O26M8CQ4#4ozI!F zoWDf`C~O7~_F>50oEOQ6T)F-mrKo)-3$w!ZWiTvxCJ=;KDiot@m7>zErR9|~8zUFv zz2ahHlZRnZZq9opI|ib7HZ){J(N8Ms6gAjh`}BZ4?yiM&Z|&}s z7eN^=mu0iM`$zg4*eo{}Z!R}t^PCS{DBl2F6|~=BE+!Hye#~*%$wc32a&JKCp89!} z@#GN5`sIVi{wIDqVYHc)YMv8|{bdr7esPahtmlV@RNxRA>bp4b80CIyu_t0rNJzO1 zGWuCdCv>wD?z-_EG+kC;i~S%MM_bb2XQxa$iNJRb=j3Xq>FGaFk{>mZd2MBF#jxM` z0wjZ<0K9|&SGDI#5Jh)n)A`rQ7fK!m)@-hK&EBxO!80oFjl~)kYu%-4(?+k+2z(4H zg$RKzT`8dQL6@+i49B0W&uro7jF&FavMlGZZvx5{q5iDz15i?n=mzY z`;9iug{gC|GL&O~^P<4XM(sWSi0k+01xJnP3AW~~u$w`d%0X^c+ip);!+(Q^n9E-% zf>>s*X7d(y6RGT0qA^Y!Qh8lkXQ1RQ@9`7*Xmo0?oo;pSPK{df9RI$pf*-dh(+o@}A8$ldk6Un@B;<$7m))HyhJTIO>gd$TOgcgOkqUn?ng5$l8z z8F)?7QA90KwRl{`pDl>bsLS3V6F8Lj&JiXH=UvgX@xO=OLB`!YlbW;QX_!UU!f=hL zu0o!LNX(I2H`1o*`#|-ca|{K9;9Wl^);gjL#&|X>W$BQn%eSC}$ZJsAd)MNCwnP_4 zd0Iqsf1I+y=Vd`W(IMl0q3b;}ROeI8i;TMW$adQ(Tl7>R@tD=UKZZj$Krhirn>SDc z#e`xjLeNX?LTbA1w@bZeEj2uy(l@IU3l)~aUk#dG5Q)Y83k>^B$7@gppt1E!k) zT(m+qp9kXuOVe0+@f*X*1^W}AY-5K`nPsk?5=KYfvMqz47|EJC>5P zGlRnCE&oA+x0_LN!6dA2n&tk+@1x1e_SX2E8Uw3&9Q6{>Bv12|rHoFFXl7X2JACfn zucOU)GEQJY-7_b6ulBNIeTOWx?L?&sDo@U-<;&(g2q+fbG)1SKeM%*2WHJ z6F~P8s}s(7!a{OKH5V32Yt9_qVaFX|h&ScgW^76XPyfXj%Toe+XhEpO|^vH&b3{|SYcR$GY<}kIsv-eS+l;j7W z&hQj=`w6ak0z%&i@sckQIvJ87$K^8V^wyNYf$Qf*k~W8(38exl&DS zDN8=P>L<>+meI$Ei({ySx*}!$txK#3RuMBP9cGgz6ym9@nF%qY2bAL{id<(5WPH>- zu<^OWKgy+Q5Hq!3>bu2$U)TB_H^@rM4fdu*8qPPAra(Ut5{2VtY6k2RV+4Iaa!U9Q zY+M(*16>*7`^Ov?Rhy4z=NsPOJEg`K97q31{TLu(%Bao8QgGf&ZQV6RH9kv1ETJ#U znP6fvqyPbh;XdWTCDnZzwKb(9jD?|rY;ZC~@?G63$Km4{k)oVEF7`!=n5J6RZE_sH zwR6m}bbn6`E}5q&{xPj4DikbBI$s*3{mX86>}_boQyYr((}|n}U(~H0PtB&!-sNPG zUQm_kndO3}Wt0_z+64Uz6~jNv{nR^Gm9b^5F0aO;z};;+U@T(#*8?iU4F29~mP(0D zl9@x>H}2PQYYgnp)7a5Jl+~UV%Egtz65I@D#)co?LpC~cixv_`uDMCashm@CGrt$ge(sS+=-8=T!T(-sZ}p z)6ZOq1j?zh#n3NoyfGl###O@-wkuK~_e8uCITn1jX<2kr^3zVQ5Xo8)NZ8#rWf4K7 z0nU0pHQHB%QJG}#9?#j-B#fK;di(;L=rKLuJ3t-Qc*;x^Cr{hwL{55Fo!B6Xg(7HQ zIaYsAS)ZI;=vSPQ$>&}1Zxc-L;U$lC+fIv+-8*I)PAprW_fbYoD;-&ImjsgRw-9F` zchi{~UT1qJ1^?Wtz38&d)Xya1Vn{X%Eg8-bvfr=_(_@@!QGYvGno9N~7^_y``({gj zUrA9utCG~bUlqllOnv=PQ)xpUP=n;Eu3-!CLfqZE{L_RV?3;YY_><4*>##w!mOI00 zQqy&gZ+2u4YXuE$`}U}NH7PB(YR*9~L5(1nWvy2L;ks{j9rnz5)|JuvG`(AV>~QyM zle1#DvBo7BbgJH z$ZM~nRU2J)0B`LK={N|_h{uY)Z(HB7k}*4oZzYzonorfyXA-h4cQ_6P3ygPu4JpgY z&DCtg?sGiD^&?V_rxO`%2rcdIEsU0ptfOpcq_A^o-~_J^wkilKJfSI-+EKVzbJ%{X ztt32BlyuBu554-H+T{q|8hm0^oLy_QnC^*zp~@_tLj@#b?juvFdgH&!gi`K5zQ?;B z)Jo6rEJmVUf7fmgP>Rds{D68+8wU8wAQD`|T3BA4irJThm7@vWvr`T2O&Eq~12cBSbseym3J z*?{^9Y)_6N2*EKJONL;}U@2Mt=qM5w{dIM^Z?u}Z6@0X}7m+0zDUPz*F1 z0E666O~zc+gvLomhs)`nVj*dFmVkrHB&``YXP<@3giBS;o52k|Ta#(%hhlHGr{yyF z2ph{+4H-(TeAp<_=F@)bzGc)-H@bD}D0zuB(v&Ozriw$5;P>)C$6ng_BvqK2WYkKPoRR*2Nv z-g^JS&e`Y>WuVBBMJursx}A4^NAQQoo;yDeAD-Y{TL+{b5_xLfYD4#OKVSc^VaAolTs1!d;Ytpt@DE zH{WcaszgdciG#;Q=-)z~J|BS!8#ta!7E769e$85Li^mLCiX-FkFcIGD=qtem{MyiCEZ)2}D*_n~mAtasPf{>rW6`iERWo6SO#QvV z4psWDpMkm2MNQ8~r24A2=X;bK2R5&R`&}CrIpSG#*i97k@EPTH}7ueW+ z_SqZeA~z`XP2Nw`Ou`j|DNWsfJMG1{iX+I)!o0itqCZ~J{@t*4Y6iLO^Tk2QXm04< zN9`+{6U)QOq*1lm(dz$_piw`AM6;5jUpXSilRE@?Z{Eng zt*9FQ2|AMR+4t^!3csN>V4L6$UiKlKkXh99)>JdS|Ep!g4iZf$(pQ7b^+Ac;sSqe> zeeFpLzj1>oo%cDs`S3+7{GZAd*)psAMhB$LdTq0V|_&KQwO23(3HE=4C)n#IDJdyI?|Pa2xju0V;1FVR{wRIP8SX z6YtYnjU+%oyl(6BkT&Z;R#YH~#yZVW=6Qs8bDT3TFA zzy2Qn{&ygcJ@ukNomd{GhAglBMgx+=-{AfDp@@A727cAqAbIiLrPcfmMnoS?`Zt{e z7}q<|&y(j@=!Gg}*;!FT@widGj_x?tL#n;_Ocs!!FNxf>&)yHaY<#XCqm$$?_NwH$ zO*uaHnLT4=z?U7BNk*hm-v2JY&GB>8cKaEE3Tur0j-!>=8~P*e6~_-bHzQLzLhevr z!M$b)DY##}1PKWbKDQ7o&F;L!C&$Fzbz;9*Jv`lKTt>ao&UR9aREC&zJHyn96VE7) z(oY+@zJ@Wat{@6|M}nKXee=xwOTuzY-MJi;PcMESP8jB2NoIY)V{37$j~YHBDyQfHAZ zFt_5l69AhbCLzo;e(KX)*`W|uX5Zqp8VugUR>@l6mq**@ODT3v# zy|U|Cv9il0EEe<20PeKX{ZYI^vX9F&VxQMti8nuPC&96_a(E?PzQi+`KJ>4V%C?~6 z-kWdL8JXdM7>Y{UFzs?`MkjuwD6!VkknGLLuV2H&G>K~qdxCo?=K65oQ-(EGbFX|g zxL&%n(vHL}5^%3Pm)~zwke8bwQ}aFH5v`JcOb(flbG19qLsEq)EG{Hf~(1YNxe;W{zaxW{Q|Z0QE)WOq_>sAnSd%mkF{(;(n*_R-C64?v`Erg#+?wi*1{_n@sw<@YzARMt#`j%ok=B?vxeI!pxPB!7oo#^`T%fjJ&ao z7PK>KZP$~-5Nt5lsN8Fb{RyI7R%y~(!Xah>S=g_UJWeRlB|KpLK718`vs$FmtT-F1 z{0owkAFNq-*I|>j*Z}QCG|Mj2X`pxrlLX#~?5;}|VSQYo)^e;ejh#tEpnOoI@U&)+ z9OKOabO9LqZmh)CIJ?(nama^jr)c63%zZ{`kpTJaXw|tS>5CAW8knF4lqyq5ErVOA zQg(9xTh~}6R~xsi0tEc#)~rP&od!uH`(Z3zl8Rh;Z0sxv1SO=Vqt1U{F_2$cCOy%W zQl?*4re7JYf0RGIQyRoii%wwd$13b(`T$Z@c>0UeTaT(z55wmJbN@pP(P^%+mpYFk+ zZ0aP&D<9>JMwDTUh;{1FV9N$NMAOvrWw-ZAbfxj=uDvnGcD^w&NX(;2NT5LrJ!Je; z4f<1+$J>ptDsh{rUEklcZ?nA&7He_sSlvW(ecRb|Y36YXXDrz#J)gQx-@%p(1jh2d&nO$tQG4T1LSLr|! zHEtAZp&;g|a;4*^$DYE37t?J?3P5q5m2EZ!YuRl2fFCE@&Mwd-8}d0?n-XZMi?6kZ zKUM~hikUG2XtS%41zo{mqkPWk0R}eh!h<1PF>ILEn>P(3pzf@}zASzDcigb*^7l=@ z%9gsi5zDWyxKzHq4w6caRMxXR2o^62+hBe#&AFciZ`6Ep4 zr(c1KQPC3oAp#$HXl~gUrS8j9O3C`HSl14SvG%_5};d=0iOS+9f%dF`bFGh+w4S^&B zV-S(1VyL<US~$aUkfyi`(gC9^GzY8S_aS}2_2Fo>8lVytE-z# z5-G#|Xi$!(eV_2s$D6n+sC`q5^3?Ord_Ue!Z4sBf7!-@=D_r%pk$%jvhyyP7 zlxO={Xa3A<*Ab<{2K#Krl3+;i813U|h1J*9>q$E@dkb)|X8dQgpRHp%c^jz%?=BX2 z1IkIHK!5>y(t`oywMrqahNp)|py@kU{|gja5H@S&cGd?ft~Mn_!*{K`+uV(1Bi!4N zoVe#+nkX74Pp_3fe4+p43&@^RQ98`1b!gu`%Zjr5d!ziiU-M1;t2QkU8+^D<3u7uu z@jB~mu&tDLBXjdX4DIu;l>G#X4%s~(PUky*&$Ereft^IJLtB^Is3Z4oD*+*}$U^X1)?}z6lR`e` z`d#!3am9<+k-f`}Xn2Y)@4$?%VOBDyDIP2s>zX*4;B?K{S;oocxo-8n0OA$!ZYXOG zzzv}qx%Pz-R&(hn*-DvC>!=v7HHw1$_tc+dq9m@SBk%Ch{^3vHm8(MK=J3@V>>8&S z_a+SY2d3D-C8J|0%zBjoMn_G}CQ2%+itT!fn2|A?j(a5AU z8r*(mSXR<@+py8+P?@tVXR@)7l`xDRRY5md<_3#&d+`8ynr-c!!@*IK$Wnz-h6X)0 zbw+AKW3cTPt1mfh_oJ}gtJBwFdWZ7AYo{$HWU3^woiW$suCHx|FZMQSFwdbEQ)sI0 zIkC$K=ni)ASHyNQX&hU~Wbru3jCgo>CQBU_uh>ZBtYM|qa)x{LZQIy8%S1HBr#EBt5zZBr4OiekP ze{(y`PdLygjz7AGhgFx(pRNA`Jypp{{-jKU??lkQmcwwtG4T>C|K`IBc(}UFrmy-L z^Ve2eF~gE>bxj3vcztLb+3E?Bns_@*g09j&vUfo*r3#wT!c1#2Qj$jtVT% zfSe4~pT@NP+~@f8WJ_UWKi2B7Dcoq|l*XA{a8|Grp;vx7Q;oAXeKQfat#R!T8|D`v zbTw#ut(0PE)-%!`BgS#lzM5e<_!E({ck!qzLm*Cc5jy

dz)LkSMxHp@b64aGlR*XD~IOPy)hi*@h`)A6#Np+uf;2%l!y^2vOQfoW zd`FZu8A`SZ#gR>a}t zk;2w{VAr>|h{Y(T<51BubtoX#I<(7E5qNE1D2WA&EXR!ju2FNNql_kr9QbM&cv~A*As68q%$~(s}Az{CAk%({sv?UXOwr?+J`uio%&#aQk>P zK`Ew`vT50`F`yaYT0|N-ESJoiYjm3DjLL1#JX!q>$a9OXI?vrEKyNh5}$FViJ0S+ljAFHi32YZm9U;^6jE}M!L zX@zz_{h6<2b?VuERDOT8iRaA*-~pelUD*0w3{VUVQHOnz#^GEd-y!FAJPRFaF7n73 z@;&3Bu&vKIzxP1ghBPx3jyH>Jch&9eXLyw@%_@p=iB%K}sb0?|GkVIBli+6Ha5+t7 z5y68KqohMU=|Hq1rRfxQk(lIoMbx}hNgSs+t_lc@(}UHdb>Yps3)8)zKggz9(iLI+ zn+wy|ySj%@(D*|D<(5Lrk74-ZZX0^qPqIQ5t-{@MW);CO6^4F1fSwdy7(4VR`aP$V zR1qx!OGL^U5Qf#eAh48bY44DE--@WNPWN&elthPRltX^ck5LXqquVk#1i0#$#o((Q z1@3PG@jbF`CYF&(x3&I@E=8r$=uq1F{=|>1?&%hgR(&}Kak zvj$>VgSnMenMO`ht+s14c&2S)WW=D|Kd{zAv)%vQ_ z>#{&z;1K%by1}whJZr~^h$sed$wUUx!yN+VDA%EJl;JV)3g9EO$0H*jiZ%X} z52zQ;s#DOt{|pv-?ST#46pi3*a}bR}{vnOba&}}}d2{6_6Wn5h*TG=m*pM}+aHU4^ zE59EPg0O*<1vzGFE)61JpwT`HadE3{Z2|;^HuY5e=pR4EP#lX>w80-9uDQiQM0P=q z7!4PLQ$J|a)^>KV3G%Z*MA8ZWem6tA1T;LzDRFUe@z&IK+uY#KA0lh(Qj_m_r;c##SJ#5{LfQ%9g&Y8#S%f@ zH_7X2n&Ptk0|J_XlE7(qGbC+db94B~|EMo1l{7lAc({Ks!otFWOQ+t~?Lmw7>8pq^ zkZM^yq2le`vQuJjE|OyTL>P<9YL0-8rmA|xNBo}h;V0#GkI>Rk5)pKSV^Dn5Hv)3a z711#<6%mgDi@mX6Zmo^LNob#UA(mowY`$(8CjN!IV%~z^wk& z1Vy0zliU`|)St3-GDV1A&8T{Q1LA7hcV;ozyaf1BGCiu8{a955MuFND-g2-R6LZ-)UM4kK0WBtO~WxWP0p>2c9te z7d)}@s9#bB9jtY;8c;x9)0 z!KB6^GJ;0puEN(Dde{Fee+E$ zhP-VUhyeYVsY<2r;n`VoNW+T0;ld}-W6%l|R>Z`F<})@^j)Z1UJgxnj0X5x!qMKH? z+P-+dUS;TZX)(X8y5I`bx0&@8#f~}L^WIPN+{%6KZFf%k+Ion`trEe=AVojuU6VSTLx}!!<`d& zTwFGE_nTKgJ_a&m4sIN7`A{wLLJMV^n4tF$Js27pFSTi3P=xAmj9Pt6>iEeV09h18 z;)$qf|Ghw~|Kcv5<9~b;YRCQ|b#e~Ru5})BAYOBF>SFx9VYO9g?(GNf`#f^e*|UIg zTY6++t-F`4`y*aW0Po`x3kV@-dZA2#FTs-Nwvka9+bw)9(lhV%4D1{d-r}-aNgfwL zM?b{1Ayavu{w8g?@|?yuS|%;T7cI=GnkAe1cq&~U=Sp@n_^07M^4#%V5(Tb@w5kxV zQHSEeZfz4wKkUuW26t=}Fiv7!>-M^PeeululMu~F5;w=Qw-)6sQ%M#SU*GXKtDy1+ zoauhVC?w=P;WkRLlN#WwYwt&p!pAuF?^Z*}4%UTN@cmH7ONWFY^kvS6X=e#1;q@7C zLFzbkX{~0P6=jQHiHgUK6^hA9L4+~oMl%Ge1UD@9cU#p9hX8>5tNqIo8qWNn|c@FHeSiXZg9QQnBgV)b9O{^cdn@Tl1cz^Eencdt@t#*Tv ze=3Y_OCY_l=`;~B0AOSP!3$vX>{Osg!>eU-JE!S=^Ug=dI-Ynmi|pcX{DLf_GkcKl zteCSCt9Sb;0+aut)d^z?87ucuFb! zvM7$dM-VjR8<)^ixeP*Pl_wJwgTlNISz2_l!_uXIhRC#n#$1I53C}UWz68O?B?6CY zQ5^re)qoERTZk;agC zKx-^ws}Q^S;E8W98Gp3oB1aqiJb z$iE$|J~K0;p^3aNuH4I4H^XyTpbr*<2YCT3W$}nNS)$bJz`Ibp0Dey>z#AFrd*xQ~ zM@~u>yJ;^ErTXlN3xn2&hsDyj7bh=lbh7FeRo(fpK_9SbOQ+1;fQP0xS< zATyeld&~bZ$8G(I&jVQAdBP9*P=&(VxVsHEdntQ+Wn~PQKU_X2G%~82s`8hLj?Of& zryJU!aSl9oaIHKOFABN!d8pkA6e|jl=Qfq72*d+cF2{Y2TMgi9E3gEsfzWF|! zJbytK_Z`l~Nzci3w7#xvL>c;7BD&BN^^0pV?Cj|D0Rn)gdSt8)IW*&;89w}+IxtOv zC>bzf*Ukk!;v#y=O94Evp9gVXQOjX}wH`VgVtcy)O(lv3w&^~Z@e6WKpIu#C0pNUq zd_F8jqx>^@;paPH9MP4pG8o_lX2PIiw~ou}hrPu&!KIs9PoDUNd;}X7jh&7Ju)B(S ze)BwE`JtIV2Oo)nv8jjOe_4HEiwBe_sG}PmTqHmF_wFsgsx&oY41K*X9Sp!J0G4?m zOalh=HBk7+tD47$h=>u`8sJ0&aBz)cpyCDR%ZCZ=o7eHtY33JUoMKL;w|O=S64AX9 zMdUytGEf$^Eg|N|R}qnqwy!poftC7~hq)OIJ+KsZ_dZWKI&~}{xw`5dRFd}T!)!(H z*7Ygq0SKWe5)JzI*+7paq2y%j<^9WS2!)K>QC9{aH9kIua>@s#6|S6 zf`$77W@1uSg49eP$QU*nUQ;z9Vxw{I0bFZ~<-7=B-f~h5n=po-I5toizViD!IGD1T z0_*ovz^4MnddRyGQQpx8JljNBZB<791EOK{%c&g;)fcn)4Mww5I};%*#SV9CPp^Tm zfxH{>r!^e}3-?Hl1KqbTrZL(fn#cOEY|T0s{ojqnDnYPMEGPQhZnoq&zC2N{n25jdaz_m3 zNZR>BEl5$MjYnfz+}Xu;DCGX6#sRATn{Zo{!9co>re%N;u3M(XTyV?JXpt9;mDbd`knA0b{JKFfi2E`VU+4N1R@U_eZ7G>? z6VtPvsiE9Z8zVU)_UkXs3BoOLSLAf6;S98Jf;kX<>-cAu?BX@b65dKvsY2qlQyS3V z855>e&&DJ(KU(DnS|gou|5Pfrb@O&U6`(LVkP0OL8HA3SVt#RExkVZ+jN3hThqyFO z-fr2IhSoR_A{VJuAaPsYz;Pn)fV7H~g}BV4wfLp^ir^ejh7r6I)`bBhV;_u+!0ZS2 zk9?^bHW+#T+VHgQzS(`fchha*{YIv?`$UZ3vM`ouU#(u`9cfyx1*T4c&DEKIl{H1; zzyPrDt4p?=JxLk+Juvtu!~I~m@_CJJS~)sl>5bV;wGfSe;WPs=@xW;f{WCOHlK+h; zhQ}=bD=3Cqg`G^^Qv~FBMixj~SCI950^){rUxuna*b?&apm#j6%vHdQ}{yT2uibB_1Dgd>0h<6xoN3G5a# zfb@Y%B{psIeXFRxf5&oS88jf5=%e%mjai&CO~9^EYx_B`@pxJYynf8ptZ2BMWkhuQ z%)3$`PsW@7wjrnxi?@2|uSxDaPsL8m5#CFdp#b`E`sXlX0R>i))l#W?*LB7;lHrS< z(rT+rQFzDS&>u7Tv2T$neOoWmxZ(bHY|3FFW0m)DRLFItY$af1}x%iV6WY{L7gK8AsC@G5B%BiCgGcivOHLj;@# zX9%QJTb!EA$`cIW5?pBVZY$-6LafQ-b~KlF7H?3cq+u50*XiJf+c>T7z!d#n%Js|B z+s8S9i5v_C0r9NH(#N}7KeYXX*PYknpPY_E6g^I-SH;6K=nxItENOp*80pvjc@$~# z-CI&wx4WM19HfZWGL!|shSX71>j61;<`cw>bQIO(y-2*G7McmYqkl^WvKJ&GSf;$mdI&qm@| zXNBV3Jgt(D)ogEw^CABWG!M!ktPi)iM(%YNBkn-y)sb9-n!-x6u7%5P!8(peZ@EOV z;%ppWd`7&AVL8JR+E0yDTYaYtDonxkDsn6JO#;TTL=(*s6AS=(!q?A&7e+`Tu(Z7szgO% z?9dI@pR|Aqwr#nv4=uwpW}KQ`YS*(my_c&<1zCILl`M7Mk}XPGDJ#6_#wGz9QcO*@ zZhGZt9Fg&8HS3kVOJUaW*NvRV-X~r&Se?Y|1E+|E?*Bayj?mX(AuoAj)KwEaFN{z# zfJWATZ(Ek6Cwf8TnJDDVUiyzGf6hu&GsD7%v~J~yl&O6Snh^ybh2ihdPOhxjJWLtn z%4YK48b@dboq?9n^L9Pz4Aw>xm2rq>3gs}UI>XmSi}Hdqgxr5bk70q<1^Z#uydfzA zWYc_?KO4K=szgg0litP`+V=qbf%~h2yLb^Tf8NV7h`3_S?Op^>XZEC#%#=wFj=y++ z;eF4~R5yF!&1_<8j#{+KmQ>kqNx!w<-f7YPaF0O9+)r`<5@c~c#jAsTEir(C{UY!B z&&pabj1ZTLFlCW8EFBBKcy#K-e_TP|p=Rm7d;x_lJ-saPVrTTi-oHMr7rYQvlrsv< zYW%BF?>xI%MR&A(2UL)k(~LZI^dfD_852FV&!Q@;Uoy%p{))pRI@$ZeQ~uXf2ZF91 zv@_L=SA-XG^E`3V+x}Ch#5pCmqs)_jEl;x8O}n}LQ^^8KS2Kxd!5T0R)NF{mJbZ!Q zcY%4eD%-oZE$?icU({cDX{>V53Vh83)8cbQnYrl<__2#r1#ON|P%JM89oZ{vQ0DnQ z=k?0xb!`=}*YQ5ff6q15)Od9zi-n)tVS_J2*2O520YkHa{!?GK1M(*o@Sv@z%w@f{ z(RVt>PiqK_w4G&%E7g9mfy({9*^Gj`MJ(rbeKJ0`@ zujSl@>vMb#z6~(@8p%zzeVVku>I4SJ!42Dl_k4OodowZvsQ1m9oPP0nirsDqJoapIY5U<4f&MX&IA3Ond?nj|Ljg{)(_e<{sK%4N07 z=_r_}miLbmlO&he8(<2Ed`leM8uMd3>8O@kX&j4whqQjx9 zS|y3~XgibpARrXRIjM6I-{Rsi7-d(;DRO>8oCi&^*sGxa{H0Ij4<*Lt!37`7vB{S! zjz0QvMz%OS<=L6FzNgnU_zs?pRn+ze)za;?RK(V48)O#!u1k7l2RwqNI}91UVK8$h zi)f7+d(f@n{cVa>gHwVz2%Df`#k6!7v-wU~U0pyvz&C>2XIuj9_iCVjpMWO&&ebKY zUB938WeA`WFc~U{5axr8A0y^#a3G@3ojfno)Ctq-EupD6KMA~xRlxLp!mymAh#dkg zMgoQ$vs^i(ScR|r(}1VFaXAcwZLb}VwW51|zRyvBE7eL}e3m$7yzzs1yMxK%avM&( zz2RQSy96ko(&_%DcNNoRRi$7XrQ=`2(u4YT{VMVNyWJYhmRM8w;|ubps}#-7ig0ej zR&4jJvrd1?Y#?LN$pt_Ybz88!fWjLX>Fq@I3x*kv{ahk9qR>Z}RrT!*~wrO!g{ zv~p0-RY-}7j0l*wJNV!wZUv(mjgCL(dc^vqT4!La3fuOJ9!p8GYdFz~XRVs?0WUy} zm!qXf7VGKGhy^~KhRe;#{Xe9i1SzTlSRI%D_NMZ`rYw|^sH3QA3F!(~N6j*YxYd^K z-a6Wo-_1=(Cf44?wYGrwH|od4~p| z6F-39ziq}@S%!;hni#ke()b-dJC0K&4S%J9V5ri!<_%ytAEr`yK+DSH+1y5UMZvew zW-Q3&8}E9K&Ix;3C()Tyxx&n>K7%;ZQln3C+)4#lwnVs6*;F>T^bq$aiaS_vrFPT4 z4G!^>ycv~*)n1wO*bNZ{Umfzi(^>;T;pn`j!tlH6{p$?bgpWU*+TrKa&+X=5EjPnE z3^@i^@7V9_?d1Q6*k4Ui<=<`HyDvFsI2T{N7_U1Mf316ec}8{MoYu)^v|p-wcbnS$ z8t4~xqtfxk)5+GFLm!0S?-WgRLf?O zkI^=EPmb* z3+3JKsG=+y>XMOY9LePSoWO2s(7nGRcDsD8)BVZ-3?F8^JsofBPcAxblsLLY2RAND zdzI}OQx%!d#eO2C12n^?p!MEG!kS~G&_cqR%<1P3{RtN%2Y0Df_cXb{MAD*dA@%sX z{oQrD?k4kX8mC5jzJbDuNWt})MzyofGpII#Y13GF#2YUcg6C(FY8Tdvee6UXBUZ%>NNc$yZ@itdP()0s5V!j5OT{6~LuPGc}{0J>97%RG70 zj?_xD8f-U}a9kmI*#Le`B6s;(xA`VWfC6E1<1xBnx~Zj|Pyp<}T5Ik-5@7!OgSv}n zYcn)hQPF6>y9tZ~Wx!fL*iFU_TPS^g`}Ip-+csv7m}MqKY1w>& z9frcu%ZDejw@lZ`7K!Em$#)oR3rn(JG!&RO-TAx*Wk@WWAz)(S1M?tw0aDcTiYAF) z9Z&+ZrF`(F&0jp==RG%?%QI$1*YULOC;kyYil0tC-M~Pzlf;J&sEPhdsjKg=)178x zFlD1U$#D*@U36{T=Z#jP$p=#Mw^2r@{qu9&7zSsSr2J?r#A@!-2H5L}U-uCf_)U3E z9s^Gl)d$}9NaW6A%1-cm?CnkQu#inVdG{%A0z8n_)|w8I%2P1C$H`e8At;XtP#MXD zdip;7e`H*s{gfS=bAoNx#znLhSI%?p7W0&s)@t2(9mZ;=dw3ademV7TTE9NSovd7J zI_u6$ICCE7R8`VWobB9S7yy|Jzs_iuvGPVi=n9kUg}hU|Zu*aMhijUzt`>(ZfZ{Ay zJ&M9uc|h1RC?Id@1UwlWVcx@3(@#vJDy&q0xvEy}?;&fuTyu9Y@Fb=(Jd>B2H_`~} zyfaMp`g)nztM-)q{vZd|K=LM2S)8SLFOw!iBZHUhA0VycYQT^lTsdc-(KIKLO(vPf zAjkpYKU%Ed4NF{Usi6o0QhsAZN<5Md*Tng(e-L`Q5-(n?7 zTryQ6GI%@T@eZ%iWu;%z0Ip4u-y$N>dfd*E3?}+=JVArr_nU>f11;Ov09!)H>AF`y zu+L+~k9?70G4&X=|KHeq�!lx82uBk={{}61wz`NUx!H$Rh|yM|wv} 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 + + + Contribution Path + 처음 방문자도 어디서 시작해야 하는지 바로 읽히도록, 기여 동선을 짧게 유지합니다. + + + 1. Read Status + + 2. Pick A Surface + + 3. Open Issue / PR + + 4. Update Docs + Proof + + + + + diff --git a/docs/assets/readme/conversation.png b/docs/assets/readme/conversation.png new file mode 100644 index 0000000000000000000000000000000000000000..b98cbacc6b486c29827743503d82f907cad958ab GIT binary patch literal 18236 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 literal 0 HcmV?d00001 diff --git a/docs/assets/readme/evaluation-paths.svg b/docs/assets/readme/evaluation-paths.svg new file mode 100644 index 0000000..24d5956 --- /dev/null +++ b/docs/assets/readme/evaluation-paths.svg @@ -0,0 +1,21 @@ + + + + Evaluation Paths + 방문자, 기여자, 운영자가 각자 가장 빨리 들어오는 길 + + + Visitor + README → Project Status → Showcase + Live Web / Releases / Screenshots + + + Contributor + First Contribution → Community + Contributing / UX Atlas / Roadmap + + + Operator + Releasing → Deploy → Release Assets + Download Host / Forge Releases + diff --git a/docs/assets/readme/hero-shell.png b/docs/assets/readme/hero-shell.png new file mode 100644 index 0000000000000000000000000000000000000000..86c800705b58a68df458401864357309ee8fc897 GIT binary patch literal 45079 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�}vIE-?v1#2tuZEyaT8o7xzZO_W_#7#6PilUTnb} z)@@IoK#705I~G;%I`;oMZ&?Z-DBzCktF-l+%F+3N4Q(Iobu#P=;t{UAJnWHYyB6fx z)@ABDb(_ZBN$kpS@v6J3ZO8tq=iDj|=oMyV(>Sr2M~Vo3v^QDKp%X!40z>_P5`frN z)UGnkIQd0H^~~3^ND`5sIXMOEKJ{DAB5J32_ZKU4V(PuBtWKSl`z@dZwVn&#?%n7W z&ovV0hdl|$`}2oO^JLCkM;XTU%XQ`qTVB)sPz9l8bC?{a0%OS9)W1mx2&il1;xln^ z=$5JPr2>d=fYehrt&S9m0`(o}i0;-z&Y;<$r#OWA;$)vcqH_M9!)<#+wQ!wISiG_A zP-x3&UnWn79=f)rqCfx~zX-{BCGJ{uF%2_H`C)T}S|L!XN-g$3$8OE3x@N%Z;| zAUP!8V+}1Dub^h_SuyvwCN*Dre)~R5zre-^E{ep#WDW`-FG-SOt7|L5lkH1Tdq@i| z_|Whv$$Jexfu%qoC(lwQQD}pbBpe=xk3ZrngF{Oyuy@s%9ckle`*t#=++GpGSv;l0;;mIVM$kCF~ zxPK5|U4dl#$DjbJ9wBD-)hn;-qh*O)z4p@i$oDGaKTd93uYATC>IL17vng!yess<# zgr4`zJTc0HkH_#YrYR^N`C<~T3@Ndxp`Jl0=&ZEnq63ZfOF)VDqCcU<27;nd??rhSQLm(2j-wJK4a5FxRLN{A#enWIw z#-JX$B=KHQb-46hpY#+J6+5|_Oz*xNv}P!%yXp(1OXRZ^=E!cgiWza+5j@$eZEt}- zqHsoBwDkB7xURhaec0Nn!`BUz8ZdejLlK)pm5l9&rw977h;fmGAlxh)1^f$6QB$(@_4^IZz^*K75Gi`~~2 zkgG>IGP7Rh{gu0~iyEC>5(3I!T%BM1eu6xu=aT3aS+LYPT^EYu` z7UeY%O|yT`e-oPpQpVKrU%leX4be!dRKoOZMhsO9MF zcAsD;_B=caW}R>GFK1&)_z3%3UQ+$beJGeu(k^QMUr#->c)9U?e;W- zsjI&wTgMru`UFCu!77`sl)gaMAa^b_mcF37Qq65j!hrt*xO3O=u7NIskTl6-vugqm z%cs`DFL;(-W1<-{Zehc5*wxWYNl70fbUS=Mf%`cU6zo5%7ud2_LPU&xwd|SnspP)GphfU990+3lkCUF6PFaNTptM(J3Nm47cVuOut6fu0B;)O z=M?giIeH90s0*?n;cBX$&ichyn`u8tMXlq@2RyQ5o?Ow}o1)LwhIBj=4;|Xzw{xr2 zdRL{+8UvsIuV{6o8YXV$El_ZE9#Zol{! zeo@Y;q22Co&{uk|(MC{rlZIp#+F8$2`RK9zk~O5(buW|pZ2PZiBHj!f8n5V?3RnW! zY994>yk)+g9mU(0YKt7Mql-kIBd@N!JgWjEuL~>iY4Jkg^L^uQr!A&V*IWdsqn~wS zP#LRnDNi6?j@{prrXa!)4^1$Yfagt%$D(f}#OvVqJjsP=zrN*HEFjuOSnqtWi0`vt z;k#;fFzE3StXgb}YojEFtpB9u^RJ>Q4_LC+Dz~?_wXJ>JovE>7B-NdW3vCbZFO z*;8oJXf?ohqkT|$Q|c=4u|11ybJ{cgpzn$zJ~-3#>bzXA+5}!ADT#vbY)vyyr#}}J zaJFFSU@^%Wi&3Dta7|PLze%0>>tjt3PbQZ2j=#+c2>%{xxLL*Y(c(U(PoE?#5M{YX zzo`dw(UjN|vyr6Dt+EDfCruiX{EDS$ag4`oaT3W3CR1y(@$;ho^kq1%O=F6Ev{W+cD{u#(J$D1vC(o6YtG|&>V4DDN|-V}` zBSt5B{++Ou{SUu5IUq5nuI|o9e}Be##7oL?zVgfIdyS1JIY$_%ZLlEMpYRH!t+Cbk z3vP~RB+E_H)RHbqc)1p3m6X)tAd487e5}^re8+D&v|4zpS%J-V5;cEPfPb**8P0-& zHKeR{jV@(e?#FM(q0ztj1U%F~f7P%5dmrHZi257g?&ju33ze7OXs@Bf(C`A}ESLTPZ3~5&EZ-JviC8a~Y;U@^KJ!RZ z-Z&O^O}kBSY$d?GT+balilN)qth5oetCPP&Q=aTtU3R>vQ8_DQzIpUrU(@9Z8wtUc2yM)%jr?rR zahw)L-R7pVA{T!xdMIVMDOhE5tY!9&3m#|nI z@rTG~jc)M586O1N#GG{av4o)gr04ss{psR}l;7-32T@5@26WCp^)FXvY%sDIt8wHY zvFByhIbjV5toY|Cl-oqyydUx&_IBC z0393ZRrzw)ly1*4AICFG{1BSSeX7PE?{HFh4(kBoA$H~=W>xS{V&+q0ef=Vdmz%xB z?*I{0F%X1Xj9zmPpn6$_?Y7lOV675OQ+sM?=N4YyA@pp&BSz1Q$}av3D&#`qLef5g z2_!&t&F&(KLhle=n_yC51JjeWx{;+#LHwG%0!4_J|S@>FP-6gZRm(f;K z1wdjoFMOl2>+f~8?v?qKO=wn`ganPzVnZCsTpm>MJbPbUcx5LJ^KcrtxZvp@P&VY} z5w2j&Fn4m=qtW6vH(g3s^l`VI;)=W3=(b_G@G~tqH_;fD7sUln$FM-WN}KJlr|>+5 z#xd*0e7lMozSl#>edpGFJLj2ROfrqLUc3WCv4;Rbon4J96m;yW3+sr^KPt$)ZxhyI zdQ$Dg?@LQT2#rX;5oX%+uBIBRtH)=~{EP6i(oHQ#p$4>8V8&Mss|RirNgPYp0`MevCv|fnvwbF)xThuCaW}`C04LA%WV^z{@m^P(#<%rgh-e188jrI@l$|1z?*K4I<@Q|7Z@u zsA5*RkATz)z)*R?`9}Z2(G!GfdH!rkRl13c7YRuZ5=HbKO#J#P_eTvDaufFv-hRNu zlG!e_bEN|<3jj)O+%qqDwtGfMORKzd4Aozo)imzO3cDIxN$ha{C0kGwq`5l-pWUfg zrH!koL0l&Gfm=&W&Zg4*O^zL(m;y{;*atMB7G2l36I~2Leml|gMz;4{*S~z7mXGaN z9y@#C%B|U4SW^OxGCR^@;@%z#dkL_<`aBBK^ip_gnL$)YWUE={+UJJ|<;JB+a&;t# z3@ER2Y>o^MT9JqEjf*GRuJU9<>ZG2wy@{wo>|q^=%i*qHvL1_psjjaw5b@LP7E7LAI#q--ryKn~#N~Wg>9%RNRt_AX+b)MT5ksZ3 zs{(KJT}m^j3@oOZOYMQI_&0}*F2f@VvqJWW4%4?iNwbwJdLpLJ2W)fwNe68XhshKU z{1E!**>MoBey49HkLtgkR#WGXH~@ZZy~gL zq|(-liP`D=8ZEHw$G)h`50U7&=p>~D!MSXQ&>wyhRg{AEP9E7(Ooo#v*XXq2L~9Bc z)WPpi#L3$od%rj^c>jd;+VgrUZLms&KLy&`6_ES9Z^gT;Rn5QU#n7dn4U{FZtTiQ} zdqB(b+11J{cJ1N|dEMnA@q3n?5E09}mFYXv@mo&Sk5Z1FlK8K(z`gViF9PfS+Tb$w zn{9KhoVBB1Tm1iQ1aGph*Nm}MKu6=vyYzIP(2q?`v817dP`@W()JY~ z+qy9rZzoNi7A*cPStY+E{q5>dE76uGaouimBz1-`3(M9$QCnENoM=re)@o)mnRC#! z=6RiB?Wf?G{QFGxpXuu{BP5UKRZF;OIIbEG+;y~kjj~_RcJp0l+0S3=Kav7du6aVQ zpvZRGv8XmAE92T#D`EI`cK{sLaJAgqW^@gM2bCR0szte+ngyDXAy%oYh}C7UuUFoV zfA@o%ns*wK?gqFb>reCH71k_wn8?iWM4P3r;+80gC zW%^Nb6e7BCP+2r%`yRektmDAW!uJ~={0%ijL8AP1rNt5|TXnTn0B1V9vI^ZE5x1^~FIK^II}E-C1^3YU&4lc1`e5Mtr6ER!yPkwzYY zfFOMk0#%k44Zoh}f9K9qCMbA5FjJ*&mgT9|q2JA@g~}XyvG%UJ?Ag-FvcLLFE?+CS z1;D&ObGZkh0F_W};iYRM`Jct(>csWJyJPRKalCrt4ke;_dJB4J4J6Bw>f=YP^u|Jv z4QF|2a;TS!Z(Tw;10_Gj4-V89yKJ#p-dxN_yrqNGqO=mvSEUWIH+A>Uf^XDnItwZJ zOB7}pZ(-YHY3y2gPW7w2F`gIbqS+s;3#F4At{2$ha`zcA!3PPI4$ERzJ@&M0)nBK@ z$dAfqS+I%Nl#QoFo8vw6#kFLSd(F>ECYJN-Wi=A(Cm7#Grk)QLDN4A4O)u@QP1(J6 ztQuD3{zqi!ibGxbEkXPzmi;&i1KY_In=BdVVN7!pvV|VcHh)0<>F^IWhtDCo(V*JO zFEh2bpp>1yzM{{vJ*wUk>8|H9kkYv=fV{W+^d*wcO}j>q#@MxX$4u5BDm!WM>U z+DDsrUE4DLMIU#&x|C5@qFCv=t4TRG~Iuu{}%| zUmBe5WkjgS_K_-PnUKLBn%w~$Q;N8lL4v~{j&Le@mkg(!T+QD(qvkz*bM{PAxm@qu zef8O1W({vC+mh$KciZj+G5YxMBf?8>kx7eYsQlz-DSX2n#lX^3`@_P}0W-Zk#dRN_ zJR_H+_}lPJ5P8SA#K|C|bd#128(eU^w4s!-F(pTtK(+wN2UOLcWji!nJ5Cf=L%CtJ zo-0WIHn4>?N#pJu!J4a7-Y|s)zi~KVdDK$NTSmjaQ;6*pMWJ&sMoc84O(yS)UHBDO z`4m?zUMvI>j}CD>v#S9rpuGQl2Z$P)Q+5*tjpwK^@mL2j^{M|rQ%=y(Nn zvVz(;lUjUb{k<`>0eP3A{HOdq5x_GQDbYF~L5sR@%`uL31v0XLIFMTpr-cvsU4xLZ zp8cqCvQtvBDCy5R@QyAG!MNL98j?fzzd?DqwAtW)M>Zb*>lCw?qgIm38+Q}qtIx{@ z{sKy7#E^J%@fS%&>#P3jLsKLJF!JM{_?BGSgj`Lxe^KOd>l@Am(D8qrVBSG1LO7a5 zd5h6`&W+!1uLEwe5lBgYvf29jV-!V*I5hbjs3bsy##%&CERi;N_9C4~+#qE2@{zJe z3lKsHh=e+3P>@-#cni|&&@O9zg<`MSA9-0}3I1%c-@t7Caqfr*<5u^=|KGUfR|_mSr~Mys z-B)1^m#q^i3UbwZEzb7}@CEImqcz)&jZn$+(pX!M;}8kR&3JNFgKn0k$O7h=%INmB z-mX$0o0%-PrIN&+#$HmAl7dT98Fhm%S&RdRm7$!JKaZ^^Vf)J|J15IB_5W0tYOH*} z;6YsUr9LR&Tl%-$^7Q6_%bb^Pw;aAN>~rU=!GpdYO!?DSqgHK6MYY*cFSb{hjh5yNg=l zPAqXp=%w9!%$+@8`=a_aZ3fE^FdpA)OB6xhwf~X zxSDO3T95X}B}7-a^z@%JLaM@y0JkOx^sWQQH!IBLf?gKv972zh$#n0m?>Oy5S4GsVzs(q2pd0TVHst6%y#2_GMk4 zzx4+Ket}?(J{=$jwzGkdKxD$M;rAoF2SnB%U3agdfxVKe=3|`&N&jygYgrDbT3i=4 z*cR-XEPWlF-^OTuTJ6QdzgQEx5ip-WC+hJ&@nMDvD7-NoO!_ z{owx2{N}M{zP{Zzmw5TbU|TBa(6rR1YvIhy__h>XY_%DWTyzdvStQ=TWSmN6BpazZR6dB^ATebpZg$c`|YL=~(M zw&0{LAs12~AJDlTqNBkUM?r|K$y-EKBDQ8$f5@wy%^_{L>h@L#k7nT?j*!8oI>f;!jCmH=C@$^(c8&y|(7bDk9IqdSQeA z^niLxcyc##K%Ou4YYQ%6L+r7FzUg5L50P_-vJc4MWk#eu1c~ik>zAZ8Dqb;{;~!RQ zFyy6r;7=zcg!u8Ja45nsZLkxFD0=0;BulvxffAYB+*vDfJKZxuLwL_0;G=@i72mn< z@z|6{bCOSmEIc6zUxy++syA0dtF(=~{QRrP8_JiF{c;Ay8eSG^T_Zw)cV>00w?;^Fq<=-s(8pU7e z+iD$kmJN8QgGW|vF%t}>`pQYMH>MXswEgZqO22C~X2jTVfW6oK2Fj7^tABrQ@(=dH zO)syT0$aPL+PH!r;(=aWEQ86-W;bj%Ny6E*6W!d7#jC_N`;VSP3SUfVLn>w)>*sq_ zZVm_A`seK^E!2h1&cyH{C(VgpQV3(0mjoIouH}bYioBE>PBxh?m{+>rGrc;S(st1h zu5)(3f%?u^o1q()^-O*6ye%33&bsBMi9)*vl9dXE6{y9;Fb5g!}!hY)2 zURF|VAAPEew)OtutSS)F(2zEacXEE&;JH)&L|cyDB>x8vXk4B1gH!3%_kQDn%3v{C zrGd3Vp#IYUuwJS~bhjxmkCf7Y>Cu~ z9&Sjz3z@i1B=4_p@E@1_>YWH0HJ$>}2VfS&G7tY+26vHBQhxQPK5%-~o zFmje=mFBsDJoD|F`U{QYfBs0#*=zKl(h(gEo33)3!V$d?7IyyN;WL6Uf?um`M1nb= zO6kr0(`okZdHl5OPsgj63w2qMoHeL34f65fVU0PvVs3zE zM{DDWCz$x+0(%)zaWXAhgG^f-Z%h))UCQQZ^mvegKSy!@FiCY^w@i{(Lm}6g`Em~* zr6&4}@G4R1Yf4^j>9>ctpieNv=S-ki_;>x_bb+$RSVz~1c*!@_fM?8@;f?|5Q)Q3w z$TdeIp5-yjGpU=0y_p3U^h$sPuyIQDXi0LG=BGGod z5%O6zmcaQGT|)1~poEN=Jop3O;hOZ_Z$;WNMeacMj}fqV_x~fK!X!bfv<1Y8h{lyEVy@nkLoWxt-wAi8E_8F#qBYx-t_ zhvwn+Y8Q_L1FRb9zB1n`Ghk~;{@!JFO<17JZ0YdpK-YKXvtF<1T(9K=Hg_n%~^p@ZCt+74%WIx;FU_+%698nYP zWf@EVA}o&_a7I3l4jP;LtEkahp3NIp;tDFP(7o?1qSR4Rb!SV(MRii0v){>}a~w02 zm@vW4<`_&vhPi4VhlO2f)gPgVk+Je0*XcKD5A)9VCwKJO%sw8 z0{Y>7nkhJ|=5TRTRXY0bny)o(uH@TDc@w9p&fnXr=6Wp=5!X%Gg;&P5)?a5@d6Q4) z_Jk376)FsAP=0YSB6p_vTBF*CJCWjGC;?$7x_X|RW>X@3;rMhRE#?_6ejoxIDxQ|QZ(Hd$o%VCM{R&J_imDMeHeoAKV zNOt^<+G`#A={NPoH$akoVna%6;DI-tFTP+5nt=IS55nV=|39`(J_Qg3$Yw5gOVS z8tmcoVR!^G(EHwtLjGulQT5pIzP<~Bqs=Qs`t@~f^TCEyy6p|C*O3rmn>(=IpQ1#3 zmaQwVYNNK-$l`$Wz_AzRWK(Eh^RyIj?$_>wKpGhLt0mb<2-_lo zwZD^nFXg!9io(&AlQT{}q@2ju;$d*kH^F#5ky&09xJrH>dLy?G21fDs2LuFUF}x~F zEg@GK^mcjkmNuM2PyeGTGb0&^UeX`K4+)MGA%;C=p)5EF(5flw#UO5T33}cZu{0qG zo%4N43XRct&b?0%c+dP&99MLuy|*#^2NOFB?7y$eT~{4!Cr_My%Vr`2opN6TlnUsIYYdEIfagm+XsIZ}}ZTLq^clA#GcBaYpo^-~>`d;Tk zsb7ZJFx{wA_KBUr;3SBb-PNpk*CuxP(vUrx(-vSWe&BSOtJ11GR+l0RZXLfiB_7}e zIlh;;GxrNy;N`ZF9gvUS_bvIPR;0>3Iu1#znv;$E!bpF-GL~B@3sBgPAdw>bg z9kAULfw3=PPB*dqqCn<3odCqupYbk8t5a8dRJBy~4VS~4Yg-Am@Mtx)*LoRfPA7I= zK`PSU&mQOGoOb?Rq;aY|;k6hadx0YMa5Za!=Cg5!bw+_uh+kuE52|lVGwwR`9gE zXR^#4etGzNaS?vIJ?2PYfNG(h9-n=J`fq2fvUBx;S-OYy!BSB>5V@gm21Lhy#_kr# z5U?$yM32_xLc|UR>(wmnpJ{>LRH`nyrsZW%yo*p8jmpi%(MqihHKx_)Kk1rNQ-e20h*9oljLAmep&a=w;arAG-PVT+-N?d^1zGFI?fW z6b$&4^eXR_4r@w46y8+fbU4Lv`-v20WZ`5_SPFB@dTc%Ug&)MUSZAXzhi0vwMh@pt zABjcK(h8w~Bs(j~1dlU@ft$)&cd3SePCcYo?*1+lTVL0$wFv!KUn%)s2~l7dd>USy*8`-Em_t%u=)n!U(&+DaUi|gQM<6q z`KFR&WhFuSh+U+I*NuM%vu_R=59dr}>y~0`h44#{gGD+o*D5{B9KD;I=-7!1_Q>O) z@xo_XVybFtqg{jHd&bI32`dT(+==o~aQot2u7dS)Y{#P%jvcQL227>F_#iU%GyLzfFQjFM|TJr^gdwq>&JxrU?LcP#wJ#wuynkTMS9#MA^RZyNjQQv7&<9eU`_~cRaSOIhC zb1*nq9lo}Z)2N@mNGDV24p3#*V|S<% z1;ZN8!ULjsW^0-eV)Z4Xe~cQBf2nJj`VBmC7kyz_g32r$N(s0tzvXswNzisRlT?_5 zV+$@JukFsu?QmaINO&`YT{|E`#UL+&#H)*zJJ-@j)pV5yb**_Uz%iWIdwAz~@)cAx zYSVIkLG6^khXc(7Gs#G#vPKlT-QO&*m8&PzD+*G)`=G(D_`Hd#I~=5Gk8z##BPDsC zXzm&6<-XzrVWd?)s%doZKeP>N904*Zcu(jMUActBQ5#~d&nYCUd4v`7+45M0&ckNi zw&_UXXB5>>R^N&}GRjgSm@&T7Y1U!P(=sYz~DpE^vL>CwF2e{sjwo|SQx z7uKxY*l8YqDhWY#Xx6jZW)81JkMq=I<)g?2k3L@%ch3DYKJlT3N7&A)QDQnMp}oBE z%9j9P1r^u+LBYNwgI z3I-a7kV4>( z+60V276-lBA=#oGE<`A9uBU1|wTyJ1A+jf3gunRP-u?OXK}(x=afdERrJhNH+1f@m zf-Xzvr1E&C1hy{sG$VVS16kR4gBqkiVPP5h8Nkc#ILhHRZ2;H!6W1O+N?5!4Y2y#w zR!lr4LG)!CrH!Ft__)r@BhFa>U7zxRXwWIPdM2@l%ogt&JjyQv0Www%iR87cd zh1qOI+6Z8O9f^nM3K<=q2U6~#XLK>wD!FYH+001$CeX?k*1JvN!x!3HCd=D`BvP(0 zc)u)SPA5YyKvG}|b(}e^&NMbKKr--e^;sOzk`lugQ^~iYu&X6Pa9R53jBQuO zxPVi6CwT4B^bu1`zPS^BB+{W%s`0F{He#rCU3qd+f$)y^X4HKrmkJt@DI-Qr#J~zA zhrdh1^|cRzW^IF--}j?}u^>oY^s0bifHz3D>v*wawD~jzhlI~PwS$Y30w9#*j96V$ z4q{3)xP`vCt|j zn_Ax>I=tbd6Ti?Y;odR~xh8@jqOeB-4m89=%}ydPY85}YdL)QwCTI$C zSYZ>v+ENUnJ~#@{VuQ1v%`%q@b9}`u95vCic0JZyi-hT;T%&EkHUPmtmNSlL?=>?? zL*FSmpUR?Odn{!4Oi4)6>QggIExo*wb79P%3iET`_r>$Ql=kx%>gTWlFJDO-21}VQwnfY8Jmdr9t$`_Aho~Gy7p`%XT_z zBWLTxVP;xG7W>#LY9et;=+DtbUp?!}DR|Eou$53ra&75?%N9oQciUUq)DhM3XMH0K zf|2)$;0#{k5t=$WRmw_LuF~}VOqF5xntqHD9`%n-p5*sY{dDe2tC@VSnI(Qy^M&?gB zC=5@GnPGJ<1e3}n)YV6qoo&(A^CJi8=N+kQxTwmPyeFk9E8*&6vnRWea#uy$Wz#3o z7beNu^0WQpCRZmf39ol!FSNgm(1JR@e1x2Mr&z45lg9QWKvDKr6cG3q%hGqGfpRaS zj-6lfbFXK!cmECaoqsq!p$}2mG2r#kuCM@ub>_33Ed1c2ZO$@%LK1L9CcVROfGN24I3KYg^E z@dZgvKp2f>`ERzrq>1~;^V49}0(~`$(Vx$QVhTA3oh}S%hqcv*Np9vkqui6#pR!4Z zJtCIdR-%dt!f#6LE7F8nN;$uJ{3rwu4~^Dg&#+WyWuk5j?Q`Y!npN99_Op_`doys+8FTBuMk+J~2PRCoeA{p6)R*T;? zk2%MNrly8e+4=N0S}b&`#`Ls=FhQTQbekU^mPRx$>~TMG^l_AS@zpFJF?Qgi*5H3l z0qT9D5&QXJ+c&>o0g@Z&BEh}mjEuo`rg)k&g`?qPH<9zRam;R?hX*qS`x`OvqfyFS zx}uxQXbpwmsF#S9HOerni$6NHKSDkB4ytUk(c?p3SpKi}zB``nF8V*T)Kk<`irT6V zS}Uql)#@-?qh{=_Hm$88sJ4oRqDIZyBM4%zw2xIY4H6?(Yt@Lo62$Mr^Y`z+@9Xvb z=ll65$tU;Rd+xdC-gC#f??WBto*3n{hI3OcaBeSB(kU+efH!##d$QNNJ@VzYL2sej z(c{H*+0a6~%cpq_LbyRyLV;(q9`dgM9!*1-Yt@sxFq6?J*l?~|OfIr?mrpTBt=S>o z{`HSS9GBLY_~9b;F6ziYF<#D0?l3nAbdG0NY&B&t(=xdkqLa-lH+f2N={wBmxp$CO z>nZakqpnO>>dQ5(+^Vs&5Vd(kIX}|pC&YBtP-W!j&uj;EG&R z8o{G-w*(9xxrxyXb~SY-*^kn3L{gW?HOi#i&$uhjQR3MaLh5I6x$T`J(feBcg#6;F zUZSRvUX8$0tDfIG6b!pcjY z`o2DNl-#uF{H-((HXJfHV=8g)FFdy-zPuS4WxpsJh#jJ1%urb1;=-k0XNwuir!Jr}A}fgnc~ANmawIk1h(8_4h?HM)xk`Ko#ZOjA40TcPPbs7}yd2 zk^){j#9MoY`mP~Wj_R^Mp3tQSuXcL1$WUHm`oL`}G=OEvyUTXp?)we&hYnz*B$yME zF*xnEGMR~0*MXp>pC$O%R0M^#=!V>H7scsU+x@ef-m8}|VC;)hVu}^+xp~=M zK-O*t{c6gCT>-n7#<(Kg`q0Ued${|U#xniqM(N6IjGt4Il9FQUK}R~RgiI6f@BP5A zT=aYR&0MOrgX=CQ-8>RrR!<>hg8ut&ksBsd#ouA7)urpGod04DZ=Gi%eJev&i-2tmG%M63x&Aht|?b7 zQcQOtHtp89t*-gf^MBWV)Zcml&u76tl(Z8mt3dd3fCHHA%g3|4^x8{y1tSla+Ct$P zi)q((cj*_#pZ=brJS@L?y1nF4mn16&PH%AP8#G?N8y{E1gU696n}-JjhAI~C?q-Xz zuC3F3mJpSRNj097z9Xr)utAn|7XZ;pMtaxM09)YnKjohavx!FEju@Qwp>1^CL8P&9 zsf?uhz5DL|v-GA1A8f?7xRo?*9@GC?wa92Lf{*CRx28nN>e}xXCDOzs%W*qF+g-&~ zWwdEZIFytPZWie<2r)(*J4pY%eI+gc{Pdr-aacE~q zo9lvDZf%mdHH0}%C6P0tQ_A4L0A8CL1h-555CeqwOOlA6{UI0WnA;vgY51V>T0MBV z&B{^i?Z@5M7ka{Z?M+lZk_6uwd$b(<7&O?2`{2b&WEV>!eifTZ-urEMyUfIu^aP5~ zE)lKckXhCk8{NP)v9k}rm?hx2R^O-Uf99VbUAjq=Z6@zAaa(30f6k5w2{y^T|l&VsWJJND^nNRD<1DOw)=?3M*U_fs9DbX?GoN<4E-)! zJjPw|@AAN&rr&e5z(T6jXBWSL!d6o_|FFd;7QHU3%lVaG1xe)GWK1WQzf$d|S)dWR zdKK%+8gD4Y79+fupcC_tevgWP;=Lf$;EHbs)VLg0sEg5hAm{3bw02;*4F-#ginr!- zbBdAtk7{N`6s7xQ5m?T0d`w4VB6dSnH+R_8PrwdgfpYZ@urk_7i$eRQ3>n@JVHT?r zZzUSw*m;Ir#1Jl*YxpQ3>Bc>!3;ToRSSDTa9Ibi|qQOpYTu>eDDs}H# zRQKG{Kt{fiP?yE(wNB1BmE6+K;JQ2{C!;Den`3pxNT7Som`nDrrXazX#0w{u9z8e5Au5*QcSC)uv{un_s{GOf z^=+TI(;>`p!FPq^A@|LVsi<%3#(Zh5gW0V1=ZPrO8b}M^R>+;bOENgB8x}GzM z!EUWuV+4%6N7Aj^-VR-O$$0U#u1HXY=|35X!Z`Jf6OkMgM#wS$q&Bl#Rb|9bx{y=+ zea8pkV#8|$-|G2Aj0Bfgk^YkqqOf9C#jnk=dZMpGrFse4exP^aY0>h(!c6`|-pVqh zD0epD79M@{?RH|HBkOGuZ`P*5YbhlyRaFk}i~pG&Cp;Kla<>{tTgd+VnQ9Xgw+1dV z(=6!4udD;}gg!7{gNunlB}pK%2O1n%?>w9Vx6TZp;CVKn(Y<|x=>fgQ*ypl$l2SOg z_IIyb`S{ilZiUbTb`;Vw1s4`PmpWRyOVJ+a`ao?DDk9BY4$2|%pxK>faOau&E3mm` zWeMUsc2qtt5ml3`I>P37m}~s5VJbrKODz z`T7l|lC?@pKNus2m*MYq8EA2aW{7MTzvMn=sw^3qFQ0WYTp>mVxi~j)fk^^Kxp(VZ zG^+G^_F*QSk43l zv_zrkU|`}>=w<=j9%W`I;g9r6J48-Iiw~oDqUR5V1?t zvw{I$7IqrZIV-UkCI9ZvC$C=3)Q-0%1ea{@6sxxYaNwS31O zrTJQX%Q5+wb!n3f{AaJ(kGdV+q#~`o(BqgQN=_u<9P^J}9XNZmjaw?`lHDkttamwT zL&@_UDx;y0(*WKOS&;;Yqo@ldz*<6k6&~IBqMq>hQWim(MBA4!Seh=Y+^Ww&iyBz; zr8#Kr@HiLIJ7K36;rALgU|U>?!;tZ{#pkDE*}|a|^mK88vse~`dN9!=%NYHm2(9qy z>&9MYvuDjes;t&O0-EOXVh8;fDzqm9&njZ#>@5V++1&`~Gxmer>PZ!wT(1@dUT zp3>A(ED5bPv@$$Id7a4IITwpVqs8U3qR>z>cw21Iz0V}8^Eg2FRJCgOQOKD{!^)DL zQElS8(FsN#XoDnnR?m=Q(GlUZynmR59H;6+5q3*nVqLJ1$yhOcKuX_C{5ZE)3aA$G=i6? zZOVhMluNNaOJPFi{nf4N(H)By(eX^yC%DM(U#v`hS8pB|U+

zo~{VT?p%q2hD50{YO5x)Gy3sN6NW-JE*x0ZGMp6+s%XmKFo(*OEoi%W8DXZll*!=G zY5k)gt*mo|ISx+>mvtVI;JfE9~=+CMh{0hqf$ExVR z1xtY>wKB$uAP7f;UU5dnedd;K^-kuz?@>7(7a2VSgEN3Ss-5c}@!Uc_t# zAFtMRJD1j0{V{i*84-lZj$VM1S(>jQD=1aAu!Z``da#vgIa_9I9N>;ujRi6SI!JaX zuU6Ctjnj(I16<c^!ABOivyXFFkd+NE=47 zs~j6pvy@K6yL$6i`c`r(0Q$K?UZeI96{&@z(Fx{^`Ub{kuo6u-J`&%l%<}zam(~-^ zX*Cf#L;9Cca5@u**7Tza3(CPdKV!$$)MiSLbB|}6J3XSRX8?t*`A*S%n~n`o-q!Zq zKOsza+!?Ky_|;?iK5b7F0CGm3_EyDY703sk%pt~((nEAjc!fEM*&wv7d- zCj;bQHnt`PTKpA2L!Ma%0B0uG84sDAWW^6)MC5u>7)CzmZMlN@Z};Fos3dm9c>Viw z5mDcbjh|WqT;->h#gw{E=wwJQ$)V)33j{kS&zwk%&)iiUeoMtlxQ-n^Y!~E)Ja28_ z7nNcFHAoLuyQ*(2Ye-cXRQsCI);IS%JM;p+PSq**j!^gKndXhZELyy$;urpN&EDe) zN`i&G$H8`=JN9@ef!Pzd&pdu+?gq1_Cq;%c`XoW~mwjN^geI<`Dw{gTe!Dvmh>>>$ zFg$nzHjdA8Lqo`*QU7a?!^%tOC02S+MZ)n=T4!zTvI#$QB3zu!hxnTl4RLQW8V5Gd15thCI!ZNH987JLy^d?!?FURNmB^?_fK z8l8DLTYYnxOOU;hBaJ9873h=CqAkyC@Ah^?d3j3uO6?3~y<*R8-KzecTa5P-g~{rO zxbXtQZ>R!FmFaX4w=i7t9u*S!KK4iB=SGa$<0uvByg_Y-GVBMdBH8QLP(As>f`Sj$ z;;tC10fr+th%StaZ4|)jVD=38<<#fu+8c8jRmlyFd|%(Ti85uUW4#Kh_@tI6W5%O~ zxQYH83G~v`aRuLSD?PNY|6i9vC`iwvBdmrr;qohK=985=G~X@>`6?VhdhBuN6X6F{ zIDP3aTS6>p0wZ;GHz1GIw6uN{k1TEoPf79$MF!l8A!qGfVGLa$%(%I@Wm|fquniR@ zF8(fea|awo)^1-0jUu$v2Kx7R-UtJT3_o}$f*M!4{8nFN))kbWkNw zcp4E&*^zbPIkB?UbM`ZnNy=xw&@eG#sE{y(n$3r;Ef47ryvSQ%*sF!5ARi3WYduI8fvYG( zl)p-0m)mBHwjK0I*ARMt91if>PUI;q9kjh+q3K;d_$fBBqytn@=POVuW9F2S{GXV1 z@YD*kd(-4j@C&xhk*}Qa7aM)xlm-ikcY@)48^5IGYjb=0BA{xW2OLb$qh0*5X?FNI zCG!NS>19;kUPn6~N-^1jegLAdn zlhp-f#m=_LP*Xi@qIsPKDabea&ebcGT|!|9!9>ls7a(JS|J5{##1ICjZ6D=>y4xln z0U!O&sWDZ(W|?YUA0tl;9YK&M3TvZ(;JG+UtZAS=5H@J-YvxR!SKG-x*qxmB&iIe@ z*sglI7dm9nGw1ttCX$qE+604*r{jpP!u&AjqRpDbc}p?Y>|{l3!1ln#GWR4hn5}VR zhf_#OCwJEKJe=EZp~=hCGpf!v?VPQed6^M=)aPZnOP*3yemI_8wYKJu2=9xIhHdU4 zF(em1wPzw%pQ%tA0z2G-SO3aqzLSZSf(Xx_Y4xI|@x^aO3k%9*d_B_7auP4;W{A3z)GcHi3AzQ!BbNCMD6;0wA_{*1bJ2Y^izlmHbOU zd@(bm^=L8~;R}Ns7s-qZOi%j?sMXTG6dZ~&=^!9v#-$Ubw*%=b(QlZ@{JIfl6LZ#f4N;Ar_{ixf|JPUTN)e*#HzXAk3HIj$R;z7vEM- zLajqj*u99?|GMat`;dN{Xx$Jxd`kItK~Q3}#Yrfya70BRF`BE46fLJ)biHH?H273_ zuIG=l0Qb*7aI=V4I{2eZnseULdiFtwWwO&D2cyvawF$H`XPlF*TpGL!zKWkh*|yhi zY##TF_&(sky2*+aM5#;+>+b z8VE!TyuAhb>ju#9?U_9Qn!n8y)s#UXFLn?p;2Q{Z27DB-4g$Ho1c5eiQ=e0gDHEg5JqWYj{j=%|Sgh%%{8dyyH?nyn97EBJ(cv$5Z1s zX1{32{`%`hXhcJ{t+f7wt?q{K{f3{mM18l(8$(Npwd8cDq-BY$)!P5Qr5Nz-!C%jR zC8xZzi1WNsOw$M_yESm^;^fpXvL>>f`EK=MKb_^8@Bce5ZBfm>cqQ^z1yt8h&)=SZ zOZG3B_isQX*KeNPAbNb=X8(1|___^Xxfeim{U-eX)gAJm6M^)3819nFfk5er?{k0t z^(KCuh$Nuz<^~POG5Vwzlq&O|3Bumod~@zRE;XZ_q{q3nqGsZvp&|TYE4ueR)#V5! zsP~szeOV>wp6RNIoPJh-_lREWWaAC{p83Q1k>DWgj5V`seRg02mLX8a@v#v~~ zOzhe`a=9f5b{+Ml4kU?MYnyoxFOrbJJ9pT` z`e4ZFzvJnMes>J(pVjESe^J85V-qzns<*VWve=8^Ap$8E6_?MvCok#nOmt6+7?3GB zwyLD1eH<E)Cf^1X$;dP0`a`jv6i+Tu%?MpSu(Nt0Z{IhzNVZLbcoF_ zpL)MPSG_}mRC)0bjwJZU617mkH6JJ0Kd(K1<1Dn$@~o<@c2njHAhe;?0k3bW zCePiEw>`!cMu|W}bQ(L&jGSB@&UY~%e?JTcsMuq-HYRiz3!O$A(()gI<`;(=()0x! z9**CCa#P0Snn>`#O8(Cg-9p?vxCEF_d~=|GZcX*3z<-Oo)|sb^`l0zFni~0>gKT|J zK*YP814mWc7rk7XfuD_^-MZd#?ez@rgctrAkQ*GaobUqCH zS9XC*k-g3lopTYDAP|paRHL?ey&Rpd5RvxF8!tin;uNE0C4W%LYm0?>uK*H*9h%#l zc}3NUWB@0plj=7>b;5MadK%Ncb!WhtA+vU6(j@u>EHer4baiuQ$X#XT4;s@3HD0~M zpgIlx8n1$CAtwJ)qtCxrt|d{jC4QIg1*bmBLE{d{u>d$Zzbb$By!&jdAA2o^)B&vH zkIJ8|a@Xfj{}m4%yf-|2H81Xq6S3dw_38b9FD)-Dc6D;gWd{W)bpS`{1!w&k&F!;4 z*Q{C5F`5X=Y9tVY95?c18Cn_3vmXNpRi`Li3j$SJ`hca${uMSI^9M%{B9Im2r1q;b z0A1N7j^ae1I{n=Y;-J4lH{YbqE8YM(dM>1qUn5?CmWyE*DT5LSG#z$u_MYX=Ql&ib z>9|ug=`GEEVGn>br>4<|HM{|de)Ki{+rq$2@$9ydPu+*>9d^9yFklGdMm5X2fx_|L=_c1I7Wht*3*afWGef zbYcqqoonvch4n!oeZCX5wtsg7(DUQ1>!sI7S0Mpeg`9YOeG7dw3kfG{V*@hDo9^#O=`8 zEj7m!V=Ket2Opj$Ncdj(`UJ|fHiqE0TnrB9n}6U}3*C3;7Df#@%NVy#HWh4la8IsT zKS%{w7xNI$?JhAp4qbmD6q)g0v7g>iBA6A zCDrIX*{FckRWXd)({PPP^-3Nd#8i;p?dV{XO3X~s{E6S7n-#b?!~QIzf9at(!H&ty z6KdWXYj&d-vYSqG+-e_|=@^dC=1qCfKF9~ket5mOrghb@ci_^77TBo@!=NiX?YuAmTm zosrb!)q$&W2V3ZlShdTBu_F$D)LQ9KZ#B(%ySy^nn31P9nKd~BV z$HJbNyZveJOixM6a*RGL!*%!LNDE%7_@3_|Ef}%8)h|&)VcC?85acNH!}9tYBnRpb1&xmOWc$tJANqBt+Vwe=jG9e z7$??U1lV7mqvM)N0J1I_u>B@l7KVj-hjL0!7h-@iK+9ECMr zX!c+IR8zgL|3#RX4)ox6&01ZLZ+dXNwu8iUd6Xw64-$bTnd?-c8>!edUxaAQ_crMNFsXsnzm1l)e}3+=H(_An~7~d3?>M ztJ%(PV`G}L5j-=;!$uZ@ipUZUF~9ylI=sb$!b(-eH<`up-(Ak!bl+}~@+a?b*$KP% zxl|cCzDKI4-z{l4pMttCr<+FwA)8!|5xm!QmrITKEcdq_cJ*7%oXja@`i+YaZU8_G zAVv0HEo~fNXFBofvqs;Cc)ACdl?yTv>gQzC!QTrFAK@+ro_}h0F+Sy)#JLK?=x4`5x^hYjdBQ z4sKGA(L;Z2o#c9zn)6C|BVt^r49`rBbd=b{2|Id9M+Or3(HKFX1KY|Bm?ma>U0lSz zAC^{`p|D))j}1y3Xx!mHLBX39C(o2R$0=seJ|6l<;cHq9_SQkLad83fj_8J%c=zsK z^-{&?^lO0#^wEIxvFj@5D;Wxpgu*ctf_^=uVstU3^Bj*sL!y9?9aS~6h<*36>el%^ zMsC5@6VLl&DhBvoK-Zx3>A_-Xe|1-7YlGllqL5*PsDdxDVf`XmiZ1s}xrKN?vr0&#}`TRG*H-lsK z(s|2u?&E38RdZuYJ=kNpIN2!U0i)eWc+q%KSE%Kh1b?Z6>0Yl~)>aWV3pj`a%3znx z1_tQFTzu)yy7^Fek(Gq+SJt0hbNKc}FmmQ-x-Af~nRR=MPQq-jF9svv-i`XvA}wi{ zsasN{G3bV$>|#b*hN{n0Gy934oyq1pF9{HEIQcyon)adp(U`z8b-T<@*K3ET)9jo6H%Y2oFRj=>LgqTTBHa#)Pz z2tO~9PE`m$w8>T!lR>Bw#U-ww{9&I68?YEkhs|C~P)YG-M3}eaQh1jDAa!eDhUf@y zx5Tx7((>Gm)V*Vh7OxzpkC*X*Md}Vm(Pg!wHHfnvC$}GRMFL^zg|wma9h~Y;e=pw}Eo$GFu603c>~!vlR;8 zy1z6mRKJB))lu4C{yu$eJVD0qP~<;lKQ#HAdbP|*o&P(+)NQMJ{YfTG^MqGPkM;UV z8R@90C<*=M;PU2_@5#+U!5XZ2y@Ee*Ih8q)b;ZTSm@j{(S+)@JWOv-zC^oHcI2H3V z@I7938ps%R0Iok-Q>^@Fnxs$-T;JX##rfu-kDdlX&YL3L$A7YLEiP3@XRle=Hr<9t zGG|@Nk(Dm zdG6hhHvpyKSs`ln8HiE#m4N8rEF5@VGA{U zE)**de$D4+MmcysoF;gcJQZ-6#)o|?OD8o5(VxgMp}xxg=s%@)Zuo0EQcSW4?1JcR zfl7$yD%kp_ft_C}o-rnedkRR)XY@ zR$*iG=(D#cziD(0y^jjg-%^#4W3bV1smu1f6kKr`BDF!-ld$SVR&|atgJiutjmg1E zM6aJ%x?Ju$^l2Z7+KRAwlbpZaVjLIrzU|~B?M^s57aXo)n4eL7THkZp7w&?c+bc5d zUtX{ss62hXp|diANSTq2Y2=AJAqQEB>EFBqx_w`KIr_PZj@1fOtWs`y^3ht~t41)B z*e~h7Udc%{uSaNtl`|fUA9}*gr_PgjJQq4c)^Oc~xipWYB8eL3o#a5n-x=}F5#;_D zLtECoOP7wg4u3kaUn%Eb8KtThYP9nR3%L~5HEG&S2W@ir5yzZ8~UH5k3k9TmP7W`;@YL9^;Rwu zLQ&2v!d<%o!DT13@Mp<-s<8Kr}y z;m6rJRg-~@)zt~Fv&V7+2NmFz{ww=d^*%x=2xNb4eun>KbRJ#aDYAtGx$XMXYwN>1 zpQ8^m#7h+-4$eaix0!X%cYjz?a-FtCQra9G{4k2iQ^rxxO1js0$5{D`=+ZETAk%lJ zKb4UeO%rd`6pbTo!B?d<3+{~KeV!PDfD1%G4P7usx#3sh@g5^AvJscNtQ#z0Q9F^pWlw zt(4bKdIHlje`Yz(1hDfM)MOe8xO7g^!v>8SEc%+wU{w))y)R^Y`)-37^$~}Gi7MPt zb7HmeJ4RGga7q7kfpKf^yUmAV)MX`TPjOcG5SN;&mR5S2`__COM7!y5?3M&P(V!pb#rLF+AGpKUCY-@99UfO|9eAfI61WLXQ zc(wu-PbgDfcG$1=${*j~kvtuL(l2Fjbn-)hm(k{w1o;E3!Z23qV6x22Z+Y4#qMke^ zPFY}#X@u-}ANwRZN_!hLnH`Xd3HlE$CMlnoZQED+$j@HEhEU8C86iz(DDpNf)ZNoR z-|=sze^QRaHDi4`CGh(RGA>%Z`JiT8$NaRU~v0wu6#{&DYKZ!9ycS^nps zF29F0t)77TMR>(JZN~{ZBU|6OUN4+(r z0u}V=z8OKP;>Y5?7LB>f6fWk)#VWzwDEooY`{ewwawgFwV`E4Vl#-lcXI5bHQ0%y2 zE%M6YaZ0L>_{RF48JRVJF7@@+Fcr(hdg7B466p36!ute297v)N& zvU|5;NnU}pv8VYA`Xzd6s9mgtg8qDKk&3b%H$&z9l4ocB$JnTjPQ8w94AW%>VCa58 zH@EzdXf%3wZ4W!%qZGXX_-P80IFcWIR$~QOMYkazR0em)~-%}@)en)#u{hGlHt8* zMxw9i_Si=CtC)EZCSYJeTj6kvns%-|qX=*&-MJ?7QG763+X%C_iBCm1g{} zpZviW0NnrMc>&rtRDdaP{gMORPuK1L!yTT<=avZbb8uo-U-v$`T8U$(#s+V*StwCo z*;yK$qg1M>aiCH%9u z*MSnZ>A3HWzBj%y#G@f zrr3MN?OoGMHEm2a482oj-kBSUa8Tud^AcPwsYB*WsI!__n>u{o&+MKRdZa18rlX<) zeoF^3*TJ*0MpYh~x5FNVhq`qYw%0vzGB_)eYk$C?8b_P;!o2`_p;W1!QgJo&&-{b( zuV+_IvO*=HBCLFixm7~~zT8}Z~;yh+EVRxx42qmy7 zau(gv%o5jNv9KmUI~bSs$Z@8TKbTgwJv}}_Z3KxPc|T%$JjKHC&0EXA;kMd+epXwl zT+5Nlq9l9XKUG1p@^n?E+FM&E&Yy8#50_cIb0l7Q18>RrQ+jGv@Oba(kF7UHXoy#U)Y9-sEny=|HGhu3M@6 z>}KL>L0Kfubv&zX^xgA%L8`EG_d)bfw*!wMi|G~v&lFi0536is%?iQEUO&*FieWO= z%Ezw7Rv<_uzTCaPvt2*xN+1WqNUdeikSF=%=+Cdo<<`_EWe$Y4Ci44!(XO$W{DIqT_YpWK`9u}Hc z1o_rnuwoFG@#1}i^7w~TI?W85_IugZ?jaE>c3?i`t+iCw-ESvrojRq=%>U5Rl+lj- z-Af$l<1s)%v8H^wS!T=a_GD^Oq&Tg@B(42hnHl&yJ&CcB?kv^JZiu|=BdU@z2?x!$ zE1{$cBw?Y`>zb&ipIb*=*foS=t_E54avgDT6Mo{{p7pewh1?=lmZv|XbXe5>NwSPT zUhSZhd;0kz9*)AaEKf=(*t4*t&>ypdefG1K^t`Urg_@7Ee)%2GiM>xdTJnG7S9&@# z)=XiW3_m3t`%|R7S}~OAOd7nZP$MiNV*CAzUo!=##wBzyMsP~9wuu>@45qJq&|P$@ z3RT2BiA{}c&k(aPsjH{db7zmq|x4F z{L3cj#kzK3m(!aTRo_ozx} zLLxz>cP<2Pf{44NG7PCi3KUQkNcPVk6U;ALE#m{5a$oXlv&icYy`qQ}y7-an_i_*| z0||S{aarxO_HJ152Vy2vRp^#?cstH|Y)mdk=<(qilFTMh8n=*p^m3hoB*%l6f&|Qr z0ZoiF1YSnR!Y`hP`%ZLb`StT9(}(-CDnv!y8471`0E^`wP;3mp{U`!)cTtU1A1_g} zD-r7Ce=7cSI5M@Z(_;+@k(q>k)YL?_vo0F&(mUpmmJ8Nsk)z=ZBpYy_Q z3GKzB__!)_LQlNYA?*V2MJ?jHkge}Q5Sj*aNAlo-{vNDg~)am{a z!+4748jiLmNL0{e9E4KKnF_;dG^V!%E5rZQXE3&yQ2JTGUq6{O5dB<_b!H7@@U$?;lb=j zJ=2Ff-sepXq^~_DSQ^eUwtb7sv2G4Dl~c&lSslTZJV_F&4Ywt;%qMY7jU2hMm6T7X z+HC0R701zK$WM(DjUNvEZptfXOqQ8dWz22tIh~b1;l4cmn}hI6dmqjXmQJ7+!lI%U* zDC2fHAW4g3d{uFFF#F_J5uqT_Z%Bf8F%X8jl zs>gO*@A^g#v-Tb1*QCznmDjV(pZ_U=;%#>4_gaY7DwFEiAb%(a#8|0(3xn;S_s~)a z9Z~L4$IdUvuueFDt0KxmMF{@>{=7WGi?tyNLE)dAxnTVFsp&Hy#R6`drs5b#LDN!2 z#YWIxqS}m{`=+cuY~7pn1_euSC>5c}2;7_0eUFOlbhODb*sGzsEMvrKE{aKzii$Gu z^)PMG-F1o2p+zE33}l-`4Wyp3@NIBV@uh87;QUozd7ZQ^u~xINbmsSrI*^MdhqIOH z#EqOUsLsjCay_b&pLyn%%D=x)ZE6-c8sWUZ?~6Ey50775Wc|a!!ow&==deoA0* zdVN}oZE{3i?nQ^zLUsCBRNuq{^kI9eE#WBy(|i1=jNHT1G&J;+AWue>9?s>U7#W$* zqpi~lmioNh6Q_Or&d@;>)|fi$m{#&9yKL@LVB?Pwi?U*W7qDKv95%O%7C^7%<<3c? zKP;%|S;j(JHcwYt(=ON zQ+UIl(&I8hUywA;6n*Ma+?>;0@$|XfbC6zplHco|bB)vlf!UT~drl07-lxlm!(HWM zy#$&xxs&E4|O)+D&e#fa%ISclkU^I(j_LCC>6GX1rftlL8uILmFafow?7Ph z97-b;VPR?yj@5MG(v$;rT}A3UnF*;{24&299hRK_V-y>|gkTP*E%yn=ZZX+?^%7(Y zi#rLH@O(ebDT%+$wm0(VT<_doT;;(mXVvDY?B6N(iPc#8 z!{lMLbb)0QkRcuWJ~7%fbna%22(QtSz0DXWmRdk~+8Es5&A*N)+u#07&-|peJ?9+@ zi?rdKOq5M*v9QbL05OiRX%3&^vZzx23h~-(X<>ZY*TmNR%ZtyGtRksdy%bMPwHM7q zg-0GQ@pA;P%N^!eHZAz5lyP51B!=D0^4-pSS);gU3BHI&FJ7oY<_-N9-j^Bcwac!gxz*SFPY7IMOx z)`IlBUL%aXQ%ZWusBtEgQgn~c=N2a}VT=;^A4o$z-x;>RVa(u6SsuK+Z^$}E)2(2| z1IAPRA;U*$y?#3y+)j}b2ze}|vt2lBMd5{zHkt!#@%~)*24^08oJC{e?r)Re7GnB0 zu-?%apJWS|?3P8^boQwP&gFNv6or(@U_Y;&a7%{;WFta~gI8#B206TbHx@4O@jyGa z4T~0jaH)|q1EcWUn9DDmd0ubznk0%%pr_cr?y*_O-!ltireuI;@n`kneathnrL>n* zY7-Y`D+QO7k`LW$;XU`z99u90rzCW&SL~sLx820-%!=Y^M;8smlxM8!;ydI7&1wiG3nGRB^d|jfG=eMe$;5 zqek)|v#BBlYo-*pzi*a&DKRi(!TBmp;LB2;Ktp^I;#YqDhjy6W@yjXRmdvA3C-0?$ zYLm;78Zlin*vFjoFoJ1dWCKTG5LS-ca)hfz2C`u*-gkqyN97@#jtV=MuY{J7b=7xr zd%-Bl-Od8=9s5;z{i72|5?A7D@3oh)k%%ju`$lo>QGz2?$3uEvM!)lJVUDJ&EUnd& z;xjN~DJb2*W*)|sVh=)1d)igKaeC)tKCgs+4FN4iiOak!n&#v2p3s5R;<`^WbzMKj zW%o0ZdYo74j8Ehw3C4t-m=&lX30&Kd;Bij3At0TXm}dVW49I-?ddB`-0K z4ALy3XijZoMu>uCWqn%DUwcLe%I7>fK>H)oB`Io6J zdxN&3Mmapf9`{pW+}-17rNBhlE=+)9@4LMtLN&Mr%tfyc;r6~;cpkHjef`38v2X76 znNq#FfQ&D5+Hts?EH_=0VUa3)5#Z8Z& zMK8qS#RLzIO8f2uv5xzY?~~lG3wNR%_ZTnRF)Y;aYUEFyC)*vqL_tyTnJ7w0H_h_3 z`t(#Dmeg!jFoApjlUl8PdkK}a^56=Mz;=g2L6=Heln|8CdN0n|MI{!q%z-02cBns| zOOJK@gUxdxh*Vg1+4o?zctOyH(;yD)CI?zb{A89QYE`V<*?o>XU108VcT1fkUge>c z{MEssSH(PJx-5>nhpA!YvJTi)2dtHT)Is~lox zeA-Jt$6sa5JgGKKjY06;-yArHmbFvqv0I0KN%b*KS9kuSxHOgg;IUgD_gZ;t<=Z}M zm+_Qzc_}KAoR}Yb(ur)C^khAabc}IzKf1-yP{BCUpp~VedKGd*>Bs9D6B|Ub^fn6- zLH>1qAyjb&jbsGz{2LVsmd9;ZuH~FkWXjMnOua}xa@+Cmazq|v^oTjPPgbE_Ii)V^ z`cA~o1SSZdsi*el6jp<&I7}}h`B2vHx2cNJaEA^BisXAvGi_}$Ji{tA4`txrhvx>y z6)|G3<{6S<6r0dKkvU~iTS99tX%oxOuEFp0t0h$RdkZ3nCBND7{cT=^!xs~j^eh6nfo|Z|F7KAI3VMDnyvZgOsRvmyuHDL zDm9Rm#0A~I-rN6==12c8CvksGy$=fbnsQwBN$2%e%Ys26iOZs2=%DTF`%%8l;FsV+ zAb+06g2F0{Y))SaI1GSAAWCNxw+h`V%BZKhorw@?assrPOcYRfP7uk(E+ zMm?M84XxU>6X;t&#j(n%%;(|hpe*KcfvH|CH5==bLHRup;7|X;1knCg$J~6x&}ght zC*6KL@Q%zyXom;LMpK3DY)oElzn&GUNE^3KM`C_C7-r=E4Fc6&Tx(%u&LdziH%9Ax zx{svLjRdd#1V!04iy*SNUy`wT5D&U)LuR_M{=io^fb2 z`2IW)bKW(BlMS{Sy+7~`?D?Y3gglV%_div5qoPIbD2s7J-@Y~(&pY>lDb8L`k@mH8 zPUe~3a>XyZnNU5}ui|Nbw=+d}pTn>+F_i8*Tila~&#HMrH?EmyzGhyAdWcng-p+oE zNgi=Pk+tm=^*~Jw1ac(4j!M{AA4=9Dlnl``#?=sihZ__iP$}qtCh}vr=xeoTq6kz| z^eBtY?;ffbXuMDj3k4!R1%-x+{SggU8Af9^;v`mZ;Y}it`I0vf|9D11DtR3T0m;qN z8G`~sL)%n!9C#{IBJ8wWf)K@(tvpdGyFiNY--*3IhH~M!jVno1%euwOLJe10qGZtZ zWCkp_YaV1)JX!;?@8?{1cG!)8^!uVB-MTz-kZweqs;Ws@dGbsjSJK6w*2t$Buo!e7 zIL8Gh_`3th(gm#)mM12zX7UKp&_<%04ZP~=c5{KCyiBgMrma4TPAH?Xr4L>?;=s-G zOgsY^xZ&K@6=hX$&(Xw4$ zL!nOLm1-|~O8|0Klw-OGsIiq?ea$n@ARCZ<6tJOs)tuS;O?Z#@ajg=iplrm)f~Jc` z;jI-m%aLa!LwVQ!uD_F7`B#!md65GNH$c<$Knwg;N=h>Q;96)uTeY-&*!$NRWih;< zYK0ex!(#a-A9RCCB27W|->$c;YO442+d2)T>T>ed< z7z9W}f9Re`a;I7nU|;|A1?N_2DH3vNcM8hEUk0zH|5>&kLaQ0_G zo0bRr(sEDaR%>NLyS~YQWCE5Uz&^Krqu_a0yqbyy&6G{2-a$+*jmt9Wn-+~1PQuUr zi{XIa7ZIaJ9qem*?^T%3@K$JIe7h)DO2|>i+N8cd=K-}DW%>*I%8AJ&s1L->D`k8X z9@x0dFh!eevg$zzWT(?Ig#oGIzmwuqS5dYf!4R4~r#ZyZ&~Wn?rlf?!xa^_Ez* z4|PE`lYG>aE~=R8d>*2(NUgGe?;Z#=Zz@$}|!~Hx?oD z1OPxvf`PjP6s7TAZre7bD1QLzN28rs(z;QI>FvEuGtueqjDl`bF`Ump6yAz@+rA*@ zk3W!LEyaEmc!yotSXgMOiXZ~LvAqs5Z;o2wqtjodh03>^@L8@fp*VE5yGE5-=?!mg z%@J0elzHFc7E-iqV()5_jcch{?QIs@&kl4vW{~*J!BN(23f;KpWpI4gOXUMlm}MWr z3S`mWWNMZsPOmU^LT)X_M>aYGz70_!O+Li!vc2 zJ>F4qfpR}TUiWhYm#vQ2tM;y{ck=n(c&)OeKNl#PAJzr}WhY+WyiryJFT7Av!nA-F z)SlevfoB(T&K@Tu+KMa?3=Y^Ok~Cj+9^RQ}fZ;jU;)IZ;S161n6Q@R;`yRk_3K!q zXi&JctbI*{r`8iCR{~xRveH^(muC@sz z&O|ud7IYf_y8EUG@G1XJ9PBsdf+uNdO+P0FRr@C<9sytf59BG(;1LBg+d2{DUSplE ze)hqkYJu=aQe9>#Z2$+oc%@uanf}E={RG(Ye$L%Dde^oLig3D_{(-qtkT--m`dg-?i}iB{E$0zqhQH9K5do z_j09?G0&aT)i>tpb>V;P`?hqoRQtDFTg6?_v*aRq+dFq^bdqO5 z2JU~5_KVB?TR{6?H8Vi_zdLi|e|d<2#RnXAm=r5XZ!x{j%l>yAEJt;2eg56q4j|CH zGc4~GDEqZ%$T;q`?42Epe5vVlrsO1T9SP$t2Bx3jnJS)se)eK@)#<%IyPIU@)_Ta< zc$9Hi?YDOI99aK0^GAvFii1Gz=TX|lWb|%edwLL1B=)c7aqk#kWYn&O_|w-RdCPz5cDRU!@$wal^Lnbvk+ zS|ad=u(5H->f%ltJ&-8F!+5>vWd3!wX(49MyAOHAGktef%H^Xj&LZihngo9ddL~sz z{80DEp;dAgwI11wdF2su%WXsr3LsAO^_DkLfnZo!R=K@h+Rid+WNOvy1S@9JEo092 z;CH-J`qeC|rvB8vN<-OHnQy((uPPm6u-R|8!8l|hf*pN3% zxxQ|=5-)$#2&f94S6dLi1$ywrcB1ggKD>WKe(v>BkYC1Y7Rmv#$!0H9AMd+QS$xph zN9K0UJP5zT$Ygftbp3KJwLp$fD+whzHB}FIx&brJ+o{dV0{he9*D^5IL-%gxbohLa zf?436_+~{IRXPozo%8&cN(P2Mfqw&UjY+ez6c}CNUD`%nIw>;FCVnLGTYGNijg<9U zl-W6j$&pEI4}I(UwjsBFo17rylhLfo0wcK43Xp<8+Z4bFdLX?`GgeTr=Y^b4-3tm! z`h|dSN21cWA650UU5tmc4LU8L+n6$C0A1rvqXM8p6L*7WvNS%4B~%J==rK29r1IS( zp)uX}%*Nu&5Vka`jo04dX}R_$OK{-IXZ8z^zjMy;Jy9Jgn^B$#|a~@WL{yq?E`T#NPn6y%7b9k02D3Lcip40L< zs50gYhbR&6Z)*Rl1nlMS0O%FZrk}$#Z7g zrHoq0l^#J;Gi|uwDtiVtNbrEw5PvU?pN`0Wwb2stLD5sPV!q0XS zomeL6;{D;Ohy=rjuQyLq&w$rCUUeNnVm9cg^}jp^El-87GEzp(mZ&yZ0(pTxbUNfodV!#U*I zIqJjSU$Fw@NzCP$ZAp2hhRw=CHsgzs+RT&7ew{DgFqIT!eYThGepi+0CF^LCy%M_0 zE)Pe!#PsQb(L-caEjPv(aWr^XZ+nZUUX9{BV1``T`cJRSV!xqq5OfstZ%h>G}I63JJ- zy1hU9HG8J~99LRv)U*6t^}i<%us&U_&>6jpsP`$-lznP^c)+U?y+La{62_A~Se-(d zDmi)LqG`oDRckyz!&f$tHO~76cFlx36D?ym$D@GvY3%Uybvm~Pw~H3bKrLxI*YvGN^#kHL1~oCwx&|I z$;sRMem$g$4V+1Wq4^x@XqZKS%k#RtDZ8Sz-!_TjVzlH3OM@T$gSvmE7w&YqFRs|& zO21av?ku~=O_Nm%Ju`Z`IFs$IEh02{;fK;zXYaZd`aM{ga8R*=VYc_G0E(@MiGCQ| zq>B`EyBqdQI4K2XTwOE2KHbp=kD?#tt@iqZ)|n77x_PmFb_2G`M6l@JsM?L{$W&&R+5eX=p;$PG5v%Ox${&;~|;ZKXfcgOfohxq!Z^PAsjUpt?DO}ON2 z{w_8E^J-0qi54~TN!uI#469e0@NI^!G6$!~%DUdb?^vP0VRTggpZ38g$mW4t_^V=~4@Ij+G9_*}^S(MS~pRqw1ttJ79#aZ66TFVkY zr!yaXUmr6TR>Vjv2E9Cf0`_mhw`YP^GImXsu%3#F@33FY(ojPi0&SwnjbK)(|GJ`Xv|qos~_9> zxlQrF^j>%QJKEaE>}@va+3Unh`L7;Lm|xOBRLF{GE%!IsQiyYF>~E7hKW&}RfOC0| zpV8n_QvABkI!0N=Z7lXF?wzme@I%6Ohr@X0r=^s$HuieXd|hwc1ZwZEO=XhkQX?nF zON+IEkw0$!x;2aEf*Z-RH9ORvwqV5N0_7RkrE{9*jXa-^{LcLnKUjWZX$}+zSD5|P zueesm$|OHM+8bJzZqS;1Jo2|r2ENUcln^>iB<55~wU50(0qoTXen7JP z+~Hr@(9`1bf%vNZsCjC8AoYkFd6r_`=ElNuRE+meLkknaLtPupS6xyf9+gE6)`Sb# zUUl_7nt{d8|CXq@c0*lTodE})>7AEoKDLd5CAI4cF`aLDgpx1+arpI2nYyBN2yAaE?uPe-Vz`v zDhL81y^9D)mtH~$igZGU&_bklke1Mr>=oblJKx#Ywa<6 z9{0FMp%{QTu>2gpV>A(04Awh?jytI%1tUr79wo;gbf05lzGqAKP{~|;*IXje0)QN* z-@ec6izkLEi*yF6{9lKzQliV^`l#edpz6O+k-wNJ6V}J1E=2}ZPEuDfQHULQAyV^< z3edxt?7PRF{pvq={CMsmR6q;zYP!>q;&N4O15_*X7dD-}+Ueu!Y|lqGNfzFxWM2TE zHQN9ZCZPp&<+ZcSNImD+colq(*sC8+fIS^>zgGl z?u;hr{~M*AFBt}(Qm`!BN3}na0DVS#KaAcNlf%EZl#m0^VL{T6xduR2#)wPX{ z!oEJO6)=D`FKyYUwDjAVNM(zD790mnlVmxPx`B)AF6XAE_lM?@{bsm}931G=nt+^) z;;1b+U7msf86n(00Cs5l>aJ$VIot;iCa0P?Og-Yp66^*4%ns`?)E+5aIY6BG6dqzQN| z9zsG5DI0R3EiwU%!H70hQXWge=-MpsBz1|ueS8y;7KUaY4QxaJ86n@)dmtwXpu0+^ zjmpQ@uAb7qvZBm?+EEbI#z&fvqoM$u#4Ipz0<9td0-|rx2tYLj*#2KmR{8H7r~gO! zFrV$}k3~|?(}MEc?`EC=j_i!bl#F>U#ZFsfx-iT@`Hk>0tfUgr8$7>ylpr0NoW`qPK-0@5@1C_r%WRCdWN} z_RKqTH`#^y9CUP#kBU4|MS(fMP zXE>bV0rC-)r#ybadbj&;6vC+-=N&^V1stPzB>fk|0aahB?bD&Q4o;<(8Ntc&rIgkaSEkj8MJ$~EWh&Bfd=_109 zD)~lkK>x_e-wA}DRv!? zLbr-rDe2q)Q1SGbpYO~NGx4+TR!1|-TOq`{2q1?Dq8HbEk~HnyZ|mDRhOC?Sq;|(C zKr}DC1$5cANpsHi&&u62f$}o~V&6_vTSd3(5iN4PF4sh5C9X6(@4%#_2bcF@I(ou7 zALN6YSGJsbCVg+K2W;)>lxOdZyK4l226=&j$U;wg&TXs=row*%S8|p zAX^|~|Az~iW(!^N>JW|j6B8kjMU_t??L#>{DGSgLz+Hv{zIA%QH7l&qkCco)13Q!-=RR(LbVE&fhtn)!?^WFQue0v~T9 zSVOYdLvSwYQTM7gY^37A5Gh%T%LWwgDrQNmoY4gku)_5KYg$M7lE z+`su`bsb>#15;-)jh84Xyxw%z&AyMOeZz>NX$-crKs?nMjEEHD}9ioJkbVkWqIyrZK*lP)4v1kj)pW(R_sKl)I> z7bYyJvt!CYymahhjD?6nVyGG@6$YXC36xPzB(!EiSJ||{2b;LF9>oHF}y26 z6sCm)_oP4g50pO&2wnrh=)OvEQWT=IuhO@vAZ_#2D@j084pi{%dook*6LB!rFpe6u zo$%EDkoK-ZqOpx&KC2SP4d4#?Nh>)4Ql~y>AcAb}Qa*=XGtYoz{Cj1Neo>PGbQ2KX zHn;+E8U+5P0-ZLY{~ycx>w-TYeynN#H3wMXO$@9~H-Xo#abKDNa?KT z;DKq3FIU`C;H-aHAiYRcpbuGCB>u)*I#jm{eMO{L^san9g0|iPbi?+5aUqp0`!~Ro zIe`98<3zIbB*cT@W~tcqfUsz|K?zEYHY;r~MUIg<`TxDK(PSSGBrdP|8bN*bu4Ug9 z?e!x=-H5KNx8?UTlUvrf^Ulhrj32oy7DjasAU5ZPx>mt2{HU^>cZ^rr%F-by;SiV8 z?#)xRScKz*qGV_4L!qaR`r||?S4*43Qsiv2+eCJL<&{m@wYif%sE}L0!PB^k5dYKe zR{4lijOb>0Y|q3Bme>DaHHMoLV^RR|3w@+H4&nPgg{-;lZgJerrDr1#iOnVSny6Ir~=FH4teG_AFLF>>JY#PCYrqy=Cw@I$H&68)%uY#7;++CkP$!&znkUH;qTil113eU>RgZBv1rEinBFilwOe5m%gvbq$M>wX0^Dk`nB z-|BEuh+J8VW^jg<6@MO+XbYxH^ZP=*ChooxQAj6A$eIcI@pcmdwkCEju^KEsnLrB8 zX%P;#iXtP|{H*91xwGZNwqIA$N>+KRO9>M`4{jl};{FV;UpPgEZ0H61Po3VL;1i99 z6o@kW4R#P`)9&%|vc0Fm(s9c>KMU6pY7SR0 z;QydA=l#G`<(6G9#zA+3g<&m^G5+JLc&*%&YNrD-nOCm_-~OKgn>R;Lh$5lcv=6oW z9{-1S6Hz#bF}%Yd=8mkVkcY4A-t&4+&6!zbEe^bAtGMnYd$x%Sk##SAux3X^ab}-w zn2o}zC$?v6cB;b16CF_4${k|Y{c*U?s^$!x2cbl_KRkTnM?M7AClv8>!1iYO+qV=8 z_AZ=qqf!;e|JY5xv1z{TAj3=yop0O(Ph16C%Gw2sD1e-@Tcy*mnhxv?r3JKeI&gXpjr>bLD-(k6!tDHSUUPWoF{WdSU zT_WMyxGc$?QB2H_ozL+s&YC9Tpj%i32*WEaJ(J!WphPtPyIM+?By$KPUEt@k~AI%%w~{ z6#@I_Plq{G@PjM{BEDq2ohQrBGSQdGj+4nCve_VCHi$ywvCX6WcPCB7BhsIhx4%!1 zR@qAr1P}cGej&&z0*DW{Vgi$I62BZT0m@$tB;4z=o+Y`9b*2{C9v{W3 zT&c&iAdT%!EBILl|5syV$hPeaCyzF5SvoVq(Kb>P|F|@=I>22f=pWBpo5?7sqXPnq>LFQ>d1?)dqOr5Y!k=HgIahwyAJcwn?WPuAdnD= zmm&OqEEgfdD%DshE*`kMl6#v@uIt=&(2TesFFv~a91#Ro$=|Vd-RfMf!fA0ajvThg z7(CMz=&s!CMvC-*@~|pYqmey+(_LJ4-ki+5#FMO)XyUq!D0Yp1)l|&Z#JgaBB?&LB z6WT}dwuj-`5+CnGE+T_N_Gq|>>aC$Axt#lo{Dg_(ltrLGwZzGRqX*P-1-iGAbRV$p zgn%U%G>~oVXRkm1;Bk4W)E(ZJc94ec{{Ia3fsbml-1*}kGa+o z&=h@icBbVMOWHA3Cys+fp1URzb0=}nC}0iv5D3DxbTnHcU{lH<5~m1xSL0YtoH<+> ztT+t|{YYX)QZQicOF7P>4UucV%_d2+v|f zHJp?0sNm)GT!$x|e^9$}U_3IFW&~Ze&nX-eFlWCxVP9D$otEXwR4Q?R zGV--HWjX#o(g-a15BGUOLUbmN8nFjf-+{`Z(3m*Gp`iKbjoF6PUlKNrR z8kBydo1d@6%iCiOzfUkCnNH!pe<63Lx-RF7THtf|XaCXs$E3-BkJkq+ALB6yw%4)_ zJ+l3%@^$rm)kEO9f@~oavq$XCI2?zJW;~Q=__JD$(c-f5J*mL|Ua5T7p(2}Y84_fg z1O#Pba|HK$SreZ+MxS;)0v4(N10_;Qd}?y!dAQK-e_$Vma0#wlUp%e5e#t-7vHW?k z?Y<&^B$iw9h#Fn%$*g_Z*})NW{4(Hor>SVDvZv*D#4f}d=?re_5!}@S)~U^$DZbnf zbPtOC3Y*L`&43V&Z5fAk@X~}8-ZL8;56{!xJ=v&I7^i+flL_00e+xaZJ;ibzj&%;P zAfDfEJ!N+r?QYssU`=T`9)1*hMteG%dDgyK30X{00-$oi)q63Dr#)k>uOtfYYvbi& z=w!X#Himbh$R1KTo;2MR>i((*8sb#XH#C(k+)&*n8s&3IRB|qOJ>)D|5Ld7X75N#k za*Jo}8tX>ML@mCAww2+bB@UlcWIk8^$-M?r5i5PZ@-Q(f>b&_Iii0duu@a`p47y|S z7M+rtzQq9+ZN2$l_zH%0{uT-(WoN#_Ga@>rBV{xYeUr zktnOsNjjwnrrz2pcU2ts%nNj!-CJI8N}I(-UaJ83@xaXi_{u{FZ~;iBF7dA5Hozp3 ziU2UGe*1UAYXCqdP%AC>nvL(^OxI{%t;pf>oxfK>|qW- z57GlJ;dx~+%V!kgb0qRF2I)QSupf8p%mF~`^cC4>xYxrgHM%8Z9>BNP$^S(p;ATp~J_9*~l z*;Hq^y1qDG#W8qBAuL9M6|>b=D~6jOr&J`jWaZ1q>#QpfQ~S5sFgr4(QoUjei}4DV zay7@Hz)Oc>tVVZ*RWAyY0p}^Hs1*1NH)q!FkT|TM3d8f|zmn zKdY~zU1Z`2P=5fhcQ z%;TWn zugrv|$Exvrm^R=2u`uR_DIsRV75Jok(pRWwrxUBJJ+-Q{>P#JGQJW0W0Ga6-2}gZu zBPdh9iMlDhtbnfI80s=HkQf1Pehw$eL+^dGDckRLH=t&DV`gK#DB(*oG+0*BWo{&0K`874o!hww zYX0k&E0y68C5ri8*{aydd4&dBpr9|L$PxEJzoSVseS4sx$5_Fc+V*!xW1NKj0{8cW z{pr_d4X=q|*+X5O?AxoFW=7+(%D8%`@XM=_n=+Khp7&4k0!2c7p4A98{PHAXU-*yJ z5bMqN84<^F`7$;^M{MGf{daiZ!Z3tBs7`Rux}}QB19FFf%%z#bg=qbFPHD80+eii1 zleh9zT06$#y#8lUgaGTq&*u4G-Sht;=X&#^xjq3=D6DJ=dYr!-_GnPnmRVE-ON2`tzeZ6 zUckQ+eyiR}{DTbi409M5YU|t%J3EYx7jMQ~+xGR@hE#Gk;Z9^B5=~m>oh!JxNjDy5 z@w>O1^B-B-3~0t;j@Az{Vfd3_Shlhlpmk1SX{p{hJvrDF^a$MI9tcw z80vDT0qK9hF=LV&FW`mwxD52iOK~`Pl_N3733pCllO;&=fDP z^dn2yn4@X!iJlA)c_J`DA&n3JyhQr#?|N9;yddw$ay?u9SQCaQQx&*4=QfjI6chKRAH1_OUCTDs zq#ooqIB0F!+T4ZN-7ylMC_)XT*-+Bc*Q|7(ZueYPZ@J2AkC?u79@sLI(H8ZmGkwUo z+EV!VmV3EqZKj@z&fDmv$lyTg3iGhHCriV%0C$c5R`;ZP+ghCS48j7osS>AsOI&K} zbT3g&omEBb7N%wM!1D+W2csX<_V`}(fm-eP6Nb_@8BK&R=-|%qw%fMgmC8(A3n}{_ zX|3$^mp4Mj`)8#+J#{%{n&ww;Y@a0~usPUH9*C>TJ3mte$MH^^gWHr6RU<;V6{T5+ zvs-xz61iDzIzQkQn^+9dymsf$40H?WNegNmAq4_1yLHk23gGsF9_h83jD^n;=eSL- zi&wvQd+w1q^j@5Jeah*zt1orKOs`7*aP4=ncF~^9&+R!S_j;-5(&~0s2A5S{3GmR|W&A&YdSyd|6Fvb0}u9ukl`6)Lc-PE5OY;%Bu zF>jy#Nwns$Ud_n8)7ibEcypQerRNFnbnl1IB9&VFNQWiQ{G;=>FzHIXF@gHm{-2F_(15N)Z zEBehF6=n?9ZySeVo&N+fx<9Y|90s!;|`84x^0^~Qs!6RZ79HN86y+pmR&+Bu3()P z#w>d9!5t`R{UOo@c1fV(;0Z>^X z&b2$>2sO_rMZR~ghvg>X@p)1h2HQg~1se0wvt}~u3Uj!~*1(z$yLrIR)Xgv&F0ik` zjUKeqJ*|QICmMM92dhgd$CA)kzpd0=D7gP0{rcr770Icyp#9ZBgxptp`r`hcq~^`{ ziKvR_$@81zf&FW9lnEnczaHY#f^8pjAS;ht>K_+N^S;#SPw;3(!r8i7PrXtH52Y6w z=gx3N**pl@=8dZtVr_meiW6D&(47`9B?FGco1>!E@FAfV{V8Jfbo3(1heO|9(bJ?j z&mGNKx5}}Nxef`%6RgLRn_I~p`>A8mEgtAhUn1jqB#Q&>*6*>FKL6oq69l1fur~AO zhl-UY9z~1?muUIkI!KkGiI9C^Lt%$-dZ=p+-qfI@mu7ZNlyxb(crns(Tk)eT*a?2s zBE|2P9>)F};Ad1Q{k7ZcV#;Sv9{2a^$JW0;bQV0w-Ul>xkU|ChxBY#WIVWr7A2>MC zDeHK|c|P-h{+(;8+Vnz5J~_w57v_2{KX-PGJ!QDW_V&&wPgng2L3=YI{2#J{4#MXi zskv*KR}7bo%SaVf3Thwr7Yu8=nY--`!sN1R?6H-fz%S4;ki~mEUg)S+iK0Jd`hlTm z+Ft;3BR*9Foen41PM#Q+c-3D&A7E&Vk(Ray-W?n>QCE-#E={ zpp-nn<8mRtQ!9r3roE(j#Na#4p9hjkY--fH~({y$>Qi%MLS-p`-bko7cyk zzI0uUUFZ6AhK|(Ml0QeJhFt;stbp>P>~ea>PA6c&`eXg?GsLw;>Tdr&v5PABsflHisZwmJgr;2h@k+QGER3 z)}yxFxw$P87S^zDc{mzmdHN4|x3wiQcm*AuAH9HhWN-ZeI6}FSN5)HIdw_*|K*AA@ z(^)a%OHAwY0+;sx9XUl%b-vC zBa9j>Cf>wlpFk5X_b}Z0H2hsDn^Fs66JGuu5Xrd8^#QD{?YuzvbHN-ffHn)*3t|S83BniuA5VHwMpPJQa3TT|>^|uuSyDZuP2%%AV>dJDr)~-PGN<5PUJ2 z0z8sXg?m<97J3_wyv-{~&yv~UCeSZ$i@Pk-SJHRIH^#-b#RNBhhziumHwsH?B2NHHOSHafdkj+-`UP;*1 zPl&u9cP~`3vEQlx;syjmz6CmW1j?Qd6Cr4e8@#-xnAX$XF`IY{x(NI!(_|6fAA1(G z-tg}I0VSAixwTl`3gXzb%7}}*T9OKaT}_+q$O^u<{#+G3Q?$XKwT7h`r&1xMM}%S4MpVS6tsGYchooFvE?0+8rH6XN{J=dII` zU-R@Ji{fJ%I#BfICYSyXdqmUI-b4=BMyBM8v1y_;CZqh^nGJ}u)Jol`<{#*D z<-Mg-jn-I+8$>9&Bt~1)X$-%@G4tF{L%x+}&P0W?H9UZwGLKkpf2|%Zx`jp5@Zd*2 zPe6{@w7K&6b!6h@ud;c0c2gu59;4!kA|7k7(fn-ckv#KCk1(X#OLMjAD7*KD6umIc1QJL{SgRJ~#0K_naHuDWM=+Q|~;`JkleSHC!LCu$JkQtVWlO|R@3ATv7o^66~Yi{HoQ`2(k2 zQL_dPRoMf{B?c2QV#t?$cdi%XU88K)@Z!2cV8~A8?T7*;9(i9iXwD`z=o-mzc2D?y^PBl*li++ocKiJ;2(JxoU-#qRnc9q#7@_XvnB z+_M(-Nv!Abli8ECRZdxQn;Q{o@JKRWJJQQ`9hAns&pPft5BF%TGtb|ZT<9@*H(ARr z;P%}KlZV4d)y|)awGuWnHK}C&%KbZ$BlbjaEBlI;F+7Ywt1rlXIg_A(9>mCE}C1 zTOZvpyQn6L_ZrWZ*6vs*yjd2XyVraG=U4yuh3t1V2~_42WhEb6W^Mhl;kP6sSu|Tf zcXg8~NI|gfJ=&p?%O|GwW}*I7Nf^5L?RFN6!`5~EKz8@Z{+(F!<0c8WiLbQdgM2AZ zH7l|P`UQH&B}!5v^)Ua`Ohw1Jorf$lBY6m(VLKy%%;n8O&2DE6SlOK5ANW40In%p1 zT{boczww}xv-%bmE3+cLr)tIldLH?b>~yy6GZA&?--|bJ1IEwSk}POM^Sh$Qcsq;u^w%0Ttkk#R3Q1JdVTD&h`q_bsuFs%w;uB@bZ6L;NQ zS_^n!cATbb6Jq`1j+ok2*3ef{W8;Uc{?@Bt8*|1<*$s;koU~-=ob8ht!jCUEV@Gu2 zpA2fdeP3=;56HfhA$PXY`ieJRv38@fvf|T>gAgnG+6`z_rGVgXR>?l?H0^k&MX)z#ELnY z*_-l7?hQFjzZBH$e|g_&Ey1<=p+k+O?NCD)dT_mFIKwj*=}_f-=nnqkT~OYAPmL}X z^ey54CK+j%RJ);Q7*c1(I#g>~F%;x=Vt>#f$~s-RvwAq?gltgDA8zm$^B-oFKkf#F z`|LINkwC!zO^Q1HSCHlZk6Q6JbM{BIb+pxVQm&PScigsYaPt7il+Edp+EXe3asFHA zZeQrEzzG`|tZJXUs-Ow>?%3MhS9x<4&>F8`%>ooVOj=VVmvc={%k>S7PeuwuO8)cA z?6dU2a*3M&pGuZw;MPjI{@f_w__Xcb_<9M$AXd(MnSD? z$`(>&;&f&qm_uwv`K>{i8DZIT0{fj0V9ObkSXs}yzWOpHT-cNjJfEh<5*u$|lP+3{ z^WK(GGLaa`s193U9u2isHs91N{Kw`%_cBE*>I27!5kK!IV3)N_*>iCL`@S-vW@lU2 z#zw~NdpW26WBZO&6LHl==W@Ogvl60@11H9Bm8Ja&7%Gg@5tc z{r<5Gsi`4nm5Rr(YI`9TaI;u``3wvQ9PyASZwVa}(Pd-Pa9%g;zn;z63G8;lL>elk!+L})$2s34aEAogxQ?DFj-lba)M5Q+X zio6U9jCO<^K(NU?C;tViQEX;+nSTM!&yjylLxMex(E55D>FyB>Af08rr8u z?>Dl!R>6ylIH9e!?!O+PST5{eW64V)LH}@ei81-rJ2Ee_!`|2<5xWrGNw+ z%@1DmPY7iJjP`XAF75R0439FKEzSwEg1&1`yrajR1(jh^tPh|~%M zWc?P#coNg5cu#&mvLscYuWQ!yALG*Tj9e!gmumkfKTiW!kb}W^731H%&d?nDQ0E;> z@(YpXT(ns*-iDPx)Wy!OxTtC+CR(5%X+?zXkI zDLlq16t!v~bKneg>jhGT+kYMR4qB9ba56YCxlLpXoUtx#&sl#rL;O>;45PlS{UE!& zMP_t75Sc}=sjP#2a(ok67$SK#C$u=R6%2MeYAGC;C*K7Je2&t1bY6k+Ko9|nW;~gw zl|0BD zm8q4}-@(eAb#z4kqb*lIiiskVbJv3r)a}?1$dJ3OC*q8tW{EV-w1&9i)73g;>dY^w zXTb{tN-MCL(&CF6#}b36!cYSh!$V!RbY7qrLBfAEU3OlNR7PEQ$QgM@nc)&i4pX_c zY8Rb05J>(Bdb+#eLDv=M?$i1DgTZ`Z#x>UKhTpyVb7fnS!~z3!aUD`Mv&_1CM^@yPIT6mMHevqrPVuc3$5vgH^$x?7sXhX8zz?qISO^iXGBZvbb z>r<1utpl78RHI`q7e6nSECr?D8m7#Uf@pwDQ&(nYO!Y4@7&v`?E@tpab;(K%ts zbys=@Uc{_DB*L(_Eps2N)ZcENieU+>uM46Gh{a?HjA*Nr^>mRtXmdl{{eFHXvzGF3 z(LUZ?plLwP1*1MgcC(1vQL0i@S?==`u({xn4v9)nEFQd(Mkl^pguGn+SfL~{Y@?_{ zGeElhANv=LTV~b2)?dzj_J)S?7FyjAnryjYvz*tJ=*e8=c$+_3K<>Np(O1_(ID}Xy z6uQT3_f=!)S>^Qn(ED9N2yzDrGq`dqWLh0Vx8<+^*c)xTBq-nhM5rCyju;IC-wEnd zwp3YqJPN#vby64+oUo}P_Q?B{{&&dw!W(twtRv5kv5VYHXwX zL&# zV|PWSufgN#ek)lXwA_R{+tbO(&&jC)L=q5c+a($99mU8tfs=Y_9Rx4$yf`!dNQ5qq ztyRu~C6`SBXI*Ixc_bio_)bwLL(;S3-C2eEhS-bIN&eLexVXc4B$rNsO3S{sR^s4q zp<`!knj)Vr(V^o`Vw47eO#}Iy2YZetmCidU!=|N)r*Q1*!Pc$^BEBSlr}Tlk1VkTCPFy|46SPfpId`6!#*kL5gh z~|}NUnnR??u#iaz5?BZL<$stcj!VLy#hc z75?b2g=FH!Ti5k8#qkYfi&j$)2XLItM&sl;1j1PnN38HMc8_)j$apw$$M5tv|48B! zqz<48g=njx?^yn#k$U40CLcH@>>A?_$5)C|W@O}YE9{+k0paLX(o1vYNEWEy5Fh?5 z9s~O@1WWaxCAxJ$F*!1xxP(un#gr1g-{oxUDzY$6VZ}AKzufW4lEZRuX2B$DKuXCh z8*TP6(WbbxKXg|Fq3a`-b-^q0ADG7PwqjgumbL$*I=;FUmJq@8bkoy!?zOkA6XPnq zL7)LDOco$-u{1qJ#fj9V`Ovz#z6Q%tDB`2&Z29xGmVo~V>ud=_tmEFCw2>L?aCp&f zU*N4!SUoP^e6ewkevSAgwNXZ!b=teR7<3zu3tUzHpJBuUsa@IDn%W|*9|?0hITb}Y zG}6SAvWW}B>C*&uBUoKq820Enb=k|2b+f>#)p{3zTJm2|VgAzQS%46^NlWleS=PE$ zJT>N$I6(9_xk^eZRZRPTCeLLkQEGSqrBM4M` zryvVA&eJa*b1^A5cD>JPVQDFF$*Y)T*6CYW<)o%sKYRAf%Xmdq~^*Sh2y27oc&FFfEB%qNl2=`t+&GBkJLDe-KE<gP ztC`A5^851T%WHsSPs&Z}{q*V6(^HD@fNnR*qOV`|sXl9y5*8kbiPt4v; z`~KeyKdyo=^)TpyG_6LazGxy>mV<7g#jm`Zk2P&_d!FC z{kPY=RF?KGKA)t$k8qKv!8cIVR@bU)uTIDpYC{?!{ zYrT*0?9w6QW*3m+zs*he=j!HsU*tt4i0>mrGg%e|iD;bk)OD{a|MHIfKMN=jcZEt*mPL11PWh~ zYSf2gx;pZ0)70~;98$&Ab<8qyOJ#beGjm-2Y`?r#eDD6Qtk3hW4bW9#HG29UCi*9s zz%7+vDuYyEVYB>UOO+4sn^`O4Qq9d>L-uV*E?00kbvzVy^M=x5CkGWMe4q3_A_aS7 z)AjS{1^NU_p%Yb%kcn4-)t!;`)j8mYq-#M`RDN0Y$&B8lj}Gv^hQ5n&YB#9pZNb;C zDb`G^BXyc|iBaVbcD1a$g9}O_ks!rdTFh_`%oY~U@9}=Je*TQ@ zH`?$*{c`T{cEd0ITr0gt!BvsiE|<#Kmb;(t)&)d!qXC03T*8ra7JjDk%B;w@&a7`1 zW+D5hxnexi&j)kEws^QyGhV4E_Wo*?4hw2$Zmy*P5WBoa>RdTu4$2y3%CDc*vK@)p zuneCOxc|)Y+bKiw_h^l`Ke~#%LCmPzkRZVWvC*fhD);zDd>_N{90Mj<)worBktSMJ z-Ey?NG;$o-OiXQuRcoH2S@jXw^8D)!j1*jFzbpcidpbh)8{83Fx@*^2xt`0P73NCZ9cMXuYN+OjreF#NIzQO$|r%g!aTj%`*hqgE4W2b}FIzMdf zjK8o0#!txO;)=6ySM)+4SDfsI)Zs?kY3dS~(r<^I`!}jCreRL?*N~{s@7P!A1@Gcy zHmktfy_zxQFZYk6idBF8UR@O|HZ71~;SPMy4GCo_%JiMm)L+2zi0p4E+!@O}rv?xx zl9Pt4F-5BKNhavK+Sj;`{g9&Fs{ki?IFH0lUQOUues^l3?du#=?p|=D<@3`sa&YZ+ zK8%W@u>?}M`>@$D74DfkM;JgJazU`Q%u6gw>8_oPYZm3f!O3$Glr(+^-`>}G?BsxZ z`g`tOT6gdf&6Q5P$?&^^#rg35eD6rvn@0R)Hl}P|sz@rQAX}iI+ienD8O#)+$-5-*b7ijXsJkHbXdI`Oz>e4WXRB;~-UXES#&oqcmL<(S zHX~(~3saA?GoW6-F5e6_`fhyo?g+loKc}3D&@5}Bd?@z}^$*B|xu}z{KT~{Rk2Ypw zRPdL|IR!|miu4v1HUb54TntlB^Zj;smygtpm~);!v}BQrVbpowJ@3B9H1F@;5^>YT zQ+XK(ZeYkL_txD?6jrA?#*Q*L&dVz(VNYohA?0aKlrdE!n7Q8v~BA5X%4W2z9wfzYoY)6MSHJ>UkSQ%*!ek~ga z$WjR5iP#kdwnhMh_7$17$trauaXPm4O9syozj0Y%;F^W{hL z2RXX}z;5wH@j8$a_y=%j{q)ASo7n;dVj4mK>GEgVhcCa_1utnd@-bYP+R9$SssFR2 z^X$T{&4b{eAmfQp1Z1~vA`r0)Hqjhiz9#7AR5SfW&vvH#W)5mSpn9>#?yN0ire_hd zcoLz$8i*?O#X2V{9P`gT0|C^=sPM9~GAlD*?8hfOoL|Dp;t+GPe#&1FrfA)Y(y59M zpyrXWv9a;Al~*amOm!(PZkH?Cz-C$}8+ko7CmcUp=ZY%S&Ws;yW* zB<*dBCp1UI?zOzOlR_XJHe7LzKA(bvo6y+Btcptck3CPAmzKHRGwaSqrQe^oG9z3#)D^_Ihp%Y~5FG*xB1lIPR8}c?JFMCg#=H373vWej}q?BQm`I zfAm{m_a`s7PcE6yUOXBA9YOfb`6HL0;Ee}kN!~x|)wz$H}I6+&?(*_3=p}=M21XU+LC$CMJQ4;jKR(ae#r+v)3+1 zP|@jM0)@VfkUV4{WBrH0$JbX^%kvAUMOQhJ7Kua}(GCIBgz#^y(mrnQ+XLlEGwOKiYlaYi5=Q@{!;f##W9ZUYUAc`sZXOu6`w zVtIL4JL};iX{AaA;0k>F{6&q&er&A2lRl-UnplK1I}#szGKQ1!@=HnK`o#x_nAv|{ z*E2NqTS<3ZJ7LoSbO!#3&e(i)^q#_NBo??iS|sbfs4D21=}q-G)(795&afqxumAy? za7MSap*-Nrp;hrL4WQLu`54|`X6%e-m+%?P*~rMqXl|CVzc5q^Jerv3Xi7TnuSoH% zAYk0zkdg{q>ZS)yb#ii=L_nc;lY8yhffJIhws|KcCo{z?-(q;*(jr%$J%0;m#6w=7 z5#LGMGd&OpAFuNru#uxK0uwy|u94Qd|FsWq(s)5pjfI`>v|OOJ&8;`GUHZQ&8{~_T ztqGY-X}`TT z$id2U|GsR_v-@5zfuTX~|2+WrqnUjf2|c zH|}Gn--gnWY;)6vhfER@+~2~w=#qv?N#mjz12Z_Kc<3)7F|ms-X8nuT zWW6{<0|$VMH6pvX^IOnR!$Uu1PoT4SKNstTSQMM6=H!UJJF#9|$UfEmdYL5RLlxs7A0fd(UzG5-KH+sR&#La zOI6rA?yZh)R(Y~sR?0FU$i@BG#qmTDzeI3#oMjl$d3Q}8c2{zzj7P5C&tiU_D``C=45g9%u;OEd=Pr)%{6_F-PR?~T$&qi;%Bjk?yG!{FID~ggmo>qJc_qY z!{MtxnI1TNn1-G6cs4msxoSeg4z>bTJLO$Hh-*Tct^0}7nU2RVKuWnQfSW1vv^>;h zuTZ&Nkej%jlGv(TLp+-ns^mJw^#(e=y{2|`VEduD&)8H!fn(}b49ulN)8_Zk<@_t{ zf`b($9L8qgfc2%S?HO?deB%rwBI`bkMi8d570-9zx)=yDYCkswIyKKS^ZRlZXb1z0 zF2C=A+lzGDSLP30S&-B>?m-u(qAOO)AlOpd0Rc|m0gPfjmf#t>T@r7luxu-ZCj4^U zt)xYd@V03Uz2~#s@4T3l%~}Cq-8+h`X>5PS9rzFpQll zH>L{c#^3(or}j991*1A22hAreFRPm_<>V^L&Y#YN5!&2&JfC}`w5=OMRoyd-<15m= z(C&&vf}HA7^@RcqJkO6<;yG9S5(2cO zD;6Jg@1Awlmk`fO>C^j6f+>;rp(B&+iMm8gASU=I>1Rd2(w+$Vd|Vg3I(h*V3Ye)% zvgRqovyp2*w{(%hY;v}Qy0NrxEAR=|8Y?&w`(C+70+=d6*wx zx{D~0lro{gapN25zOn4R`tM)VnB4!?-g!ke)oqVH$S0sk@xxC0V*#Zj(n~a`=tmVqIuSz= zFbPNrHCQML3JTJD?;yPd2!aqg2t-QgJwT`lkU)~VJ?G&*-Z9P{_u-Cl@|29Rch=f# z&b8JozcsgKS1qP_PSN(VL1ers)^R5>Wu3Kdq7-x8K1*ZoTuXUqAF%BGmJ*yCN& zVvrk5Cs&tiZe!>%Oze{jRq4*SZGm170yDeIFYH%@&;R*-2up44GY%E>JxT40A=pFd zX{agYViAF#TWFAc8AKCRXaNa4VYU_?DTaIjXDza7KNKBMJ__TF1($o^neiyfZ2v_o z%4%6G)W2=x`+kP@XoxI%FO4}Cl=Z66b$&cAGzZ!Ioskn33=8WPu4k5)t_a$wS1&g5 zGdC)g7~>;nr&iXQkkfkx;QIMbU|_HKT8Z$TceI^fg~5m0q`YdC`z1q{h);w^EtSAoZORqunP- z=x_phT>1*uZ?7~H(59!2luS7sgih_P+;-?{GH`Tj~tM_c4RmcTGIi+HLi+e%nAQGoxHEZmhXNGHClCvNT>fi!Kz~n`m-d zHi$QhI-$;bH}DIdzSxjR>gGt70E~0%&rU)wsaPM6;kk_5LBM?fW`c2la?4xlN${F^*;%YgyJbzzlLx60RMkO}0R9PJuL>r`;sYNlx+wgI)Gr-8Q__EilOAf zEZRa5e%1Rmu*tRU%W2H@%OXLBwJ)a%BhPcip;^-Z*dVJtY>`rz{da3p!BuuchR72j z@_9fwTUw0btycjQwQd&BDyKRdkiq?r?aS5P)NXt1)Kh}5?!u-gbEkXxy|qjQ3e{fI zz}p%TXA0g6eQKcet-zwPxRK&KOiAzvtsi1HCU)3(Ct$?mN}P{ws|8qFuKCt9jMRWP zO1VTv5CIMk>^)_g6xfZcDVphYqr&-m6MGYFI_PPM`F?jz?~~<;Mg%^wF9B^N3&I)b zo&^m)6wTJ+z|8m}tY7UGpX%a;cztjT{W4x{+ZWPHHSSmm;e|A4;s<>THty$J(PxWp zFkPNJrX;!8h$pJAe7e6*BsM9i9SECe*3_U6>*U@?iP(7|%rJ7j(r_Di=TpsHlIIPm zwQ}Wm%1{9X%+zo9skK7cgbqI=<>sL)%ZKw-R*O~Q(gI!zi^okJxKf+7_|}Xmw%u`9 zxj+w&3|h%b_g-FKs^OTYPqu_9*bGe@1BF+@vr zi#yNboC-I2SaL_ zazC`Ix6nARZ)Mi|N2ZGS5u5?P8db}ES)c!J9+4o{q;6AFo)itw`y3+KD0qOpe3hMD zX4{Zj&+lhHr4w$e0-FKFGKR2Uo0=>TupED{yfS)4*|vdVOf&|QA+a8Ol+55+n% z&Jn_KiOqVH<$@s1#%fHD`>hhbkuckYc{0dSv2ALW9SsAKBs30>+mhv?VCB4GY&@ z=K25_(>H;)(Wj|rh2u#Cn4|}OjpzQC zo*bg5e|MB%4{_|Hf|AxAJu%Vlk>BzJ=cntW1Uev zQ2!sjJ0E!=?K0TC=alnsH0M|ajd{GsFWkJ-pz_}Gp?$Xyb#tLi%mKZeOXb7N6g7n5 zT~cMr#u^==of`15!&~w)sd52}84}SR)TqG2T2o+4{yBeA>tVq2HTyU{2_f&7wP`L# zJ~xxy=s@BNkqNO2KQ+vOumUL`-dU2NYw zxJZPt)i<-QzGJ-(D)|IvE;HTGFLmKXlNGSQn*u5|-uJKC8L8E5m1@)lXm<+yQyhF_ zG$7}C&~~rVruW*U%uM6|oP|8XR%Ile=SlNwrwmTf@N%%<6@(&fI7OQKQr@Q3Xc zS}P?g?t9@nyK0x2&dL=^)s|^K4lw)=nr>kRW46D$w(zvftrDwpS_*Hga1TTo8?ABk z<`po_FsRU)ACiAr&e!hA-b~lx*Fz=${+UTxxM5G4^&REUkuvnf!-y;t&tV&kgm@r+ z*g~$4<5Jn&a`s)2SjzDBzei1slSxsR`t|>+vnNO*F8B~Xs} zi12pk!A#xj*BlhTno|GyId-8_(0bd6ZC(gRoAlZj0#BgPe8R3+T9OKNwY|=-4>@zm zbvh?4$wlQH6(ZA@W^oC^LF~>GK3w9zA&f;l>3Sn*-xElai3}Nl22wk- zq^j-vz;tg?%eX1MFR$a=oz3a*&I=(EdwE`m#1}Ag>h?G^#WEFljAt5y|tJ!lWSony_n`rX#&!HOToscYU-P3@DWVazEh z&nW7HyfmNRL~56vjXtTTM`Sf0uxu-gT7vlM3Y`V*0;hN|jB`q!y-n=yyxxdMB2Wk|miv?zfwNFf#aGI$EL z71^U?rIJwuDGG3-M%9K%Gk0U;lfZ1j529rl-o^W&Jd9mx7qmXRj+7hq9@m>S2#tC8JKFy z4NmYgi*r1-$Gtkzv@(n-13kQy7C%Tb@^nc(hU;~;VpBClFGD!Aa{D!~dr~Vwy33ul zxgycr<0@|2LT~sWAhz_+YjLUO;8)Xd&hQ11pz&)$hq#p(; zgyu@~N$E=}7`IAD*sYInN#g5~QN0tN0&czzZmM;rcjp+;-4?|(<^B8-9K*Rmysa&T zP|5Y?JFzlFw^DriV{b&pMl!;O=Cr}^=86~i_{6npwROuxl;gkFN95x5XjZoCe5_F? z%O_b_*?^x(HNii>!FccsWz9s1qu~_2k37AkSLA7~b%yMVed_9)SSCyWKVxP(J_{8x5sZ~Qh)1H!>f(C_Py|MZAm|B3f#7J z6%kuVUkHJh%gfxRDap&F_Ek5x?K*!_TYQ_8zdTm~ zt2WWc%aj_u{$hn0*q#y^ZMNe%Q&;Wt9ZjDnK&6RQ2L&~3=ROPEZb;&r_)+L6WJ%jl z%kZWp^EaERH+ufl%iYz#{#?GApjZ;Pnc!IT5KWdTOV-SGAj@2wIm*(jl-3BG$QB8% zpCai=D#b>>Oi_AaTvl)IEaqoYzLjHode`FoilrwW^9&qK*g@dm$%CzKt11P3DJXet z5BY#om1$)4TOabFlDv>O9s49P{iEJl-d6MBe_~~h1`2yt_V2Ldt-IJj8UFUghta~y zR?SGMdOE+(L_gj+R4J}(2B_S#c%S2=Q=rlDNFbs(Rj#D+^h#n11~NC2xi16j{5~gA z4MSNgXX(8vovHL(D%_QwkBZ9Ff_ar5|0I}F?s2v@H?U60P&P4m+s>e;)HY@njWWd^^?*+- z(7S(WIK<|VFc3Z9 zf1lg0vho%>Q(NcJo&KGI_w)mM<3}>>mIgCexg*YG^ev#g8ta;A?>SetEE--+k=OH9 zLU~&+yGu9KEs`hj242>D~S2af9&i3|3TW;VotQ|0PH@~<`Tgb|nm2Ob1&oj-Z zYi>~n!~2EDGYVeiZj=j47cuU3)yFaV-ko~CZvr1Pk`4GlX%D;!rM3p`=Vs9_y zo>N;EcpSX=KeV4a~E0uT+g~mp{nJl}_oe8i?~# z)T}1n?ee)c@ocy~7rmBS5b6>#9B6gqN+m*>z2ELRenanm8XCM5LAJu58|qU!_`OA_ zlvj8@`6T=~l+v6T8wZpp(XK8fNhK>UwSJtEPF1sIaE%HQFy`j_^V7QYc{QZ9|02G` zj}#T5+8BSV`va2${oF(Qd8mLD#OT^C5WJ33g7x@6!X*G7a zx5gw~*o?0q>ou1g+n?;tdHO|0gChQQ5&}NeY$$8n@|A+B8EJXDV%Wnb^?w3 zmIi>}DHt!(ZA2Slp|Y;M#tAP>BfL_EV#HysPpRC zJ{hOGSqPmd++{~M)NEI{I9v$k%?&{OXl76I^I0D$H61gnwWSWV%WS5~9yMSSG%|}s z`*t#}{lkU`So>HTUY2N{OsKT((?4^??Pksy0R_dUJeL(R^Y`|DbRC~scvyo)Df+qi z8|M{~9Ku5ZGm^t(X$G^9V7K{(#V3zT$V%Pu@H_10;xFS1LN55193nw|2H?8uF4J)` zn~xoTC2+0xw364i;!)Yp+;D~L*jhA9$&kF`l_)h;EaSxA^vaIwq?Ml8@%i<{hySPL;-h%daps**NN*$bss|#f$Ui?I>`)Xi#!Lrm6pxU+FNJAM)rM+O8ukG;ptIS43Fq^7pJ` zVhlmMHyJ$lQs;BscI*6B3EE-mr;bkO@>6WH?-D;XC+!NMPnugu$?0_xv6|Sudo376 zDM5KOj4>23st5eF{iF?iiOZg&bDsgW(ypad?ZO52H!p!?>*`HoelOCbwds^CMDjv7 z5O&$s7PchWXD%K)X(X1H+ZyCg)aR8pEa=tLf4t`d*S93HQv438*AiBH!&j0ob8iw* z+nt*iY95DmJ-!$~KF-?mR=r~vAsMH(G|!pT`bKwsc{o8g66!Oz{dK`IbFc1X9x563 zNImHAyW2HPS(KD|V^r^S!$e4mnwMu|tvpZ)4!y3~ODSc_}zNT9o)FjK49Yci~T9yT|#2` z^ISq;^Ik$*mIXp8;v)1l9S6f&P-aP3-5a4P3hr}xGQvLO9Jap0%60hxNuNb_?3nQB z&}2Ev!l<>c7fL4YIXP&U@YhhHUVX;I9DBNwy(hT7)B$t*#OgoB#>S?wM0N`XQfSJ< zJO9=l1qB6r{HB1BmG6mt_nG}}m}PqT{50VrR^Ge!J2&YIZouXab3+=fUNS+ROdXb1 z$K=n)&G=NT(l%)~ol6)o?9ENKyHcEv%hkvA{+*^$nquOw0fQIC;NYvBx1Tck-)t2Yjn@seg@ zsw;DveA07(ewb#uxn|e;&0vz;wV8IJni=D_RUD6Ae@C2}68&O+P^mVWLdEktto~Jm zl(^(`Rh)$SKgLK|F3mQr*RmW2sKyuae6&LtIF`+%b#;ZeYk`O23(9!9)ccB8xe{pf zxqN7XM?j&-(WT~)XH#<%6*bUL$V6RI?J;{ID8A>6H`a~4GQQ*qPyapGS|-#?rK zLq;w2DZoR7|L+URgQP#Q&@P1{R=EI_fEgE)!Z*O5F94#2c~7?#ZwM@ba4COCiXEUr z*xj_bbgm}gWYveD4ezT;*hc^#L3_OyUO{Y zoHzap^y{mDvZS5LE^9Ina*JjSy~VbwA^je7^}1T>CE1tX&YW=t5*I?iw_Mf1A}dyw z`#SoF+sN&g7tKI<-+-2o+pEb|1B_rBM(M2+Z}Ph$#CAhI;l(br+yXjr6WM77)@wb* z`==ow(|tjng?JRo zMRV(Y{)a8S*3RA$H?OjD-%I_n&Z52j%FVx(zXG@9_jLDv?S|60Y0L}i?8ExX9Keco> z`_C=f0D{SpAtGi2#aKFlT<7tln~2Vjp>28BKb!!JlOX5j&d$@J(h3zXK8ARRT4N2m zK(1GF)(;f2<;}~r6k7VkL?6ea4S+s5ahsgLz2!eGq7{=Wl&crTBJ0!y*;%}>a-bh#})X(>Tl zA%P38pWY|=TUx#ZMP$)XC=!9{rIo2igR8JqCr72v0B(hTzL!+@1dpA)f<5laYtH@A zjbg%14LXCxdj@#C#wM(Tqn7&=vNlhIE0uUq6$nyxs(?H*e)(@ERza|69^g25Mw4Xh>|ZCR~B&!_>h+ij>B}z>lJU$7x#CQ-!N~w0Boaa(pbl zlu#{IjT(4!uWh00M9%5EJb>H;^QXV6n5Fgi!@DCKJ)f-;+?_*+N`M%qhmv6ghHzqH zMC|7zM`s&#H8em=KF+&MP+I!A(A6rkb39z(F3`ZTY6a60wLU-ukTyd+RHFrvpO!*@ z?^F{62(*Ng@U$2ILmvvP3j@%{>WIG2Dj(mgd@K%mpO9#6j;R5t(H%Qm{TFxjC6&}t zlTxDjs%%(4muucVi-ugDC#+{&ldBPP{v*!@ z1&@BH0u#04T7;rx8ymvNr%kL%RK_&pgb~X8~hIM z9~kJY%_~aBiGt|Ue!5t}*Bj;gAD7N)F#!|25rtcFPN(dNHBjrV0o~FVrHAMn$l@0LZ#L!3=oh}5K~c)cI?(GNRsr@c($(}L zwgEvT8@IL9WYtTJqq$T~mQ}E-(6n0W$%cX>E)8{}KpR7Ocu>sg1edG70L*grT3<}$ zgrG+rJR@oJcLRpV4Qmtw(G78n;|SeI3(e5}qnN?n1hfH{Y>S(_?!~`I=ysl|gAn|K zo)s@VHYI{3J#e*44qL>hjcYEOs1{Pqo!Yh;L=^yN_jwJ!Qe=|jiO@Dm3n^7dAaUI@ zNk*AF(&=|8q;6OaL>AL7Vd0)S@W6LPmShZ5+seV>r}P5=*8)lOVdEB@fYj<>1!1l| zqKWJcPMlnK!LH;Qknd!IErC0Wp5q;i3}g zAbYfEFk=BAac}-v-jh=m!Zx3l zN{_H)=)azw9{okNcX*E40*ASVNBGq717LH(*4S;=Rq2BL>-07D@JB#f`pF2IJ9nt1 zC@S%-r2!Wy@vd;`h0QtczX0vkH)%Q>pyf!c6_dL}yYXl1STm;}kIo28XwvedMF%r* zF)jCQ0OMJ-g4-Lc4Cx+{sS*W*6ox?1G=86%EFQi-iO|&+JeZ4@E&(YMr`M%0DFfTn zLuJo(v45l)E95X6`!%H|0zogHZL1VCf4qJYGhvAPns}ErIWa=PQApt+L>hTYGeomA z8%pB?mcxsb>#Qh&VP7LhO4HnS{2(GVLUpP^=kgoPY)6F_ZRkI=U|?pFYYx)hT8b+0 zc<8oDsUmQ}oOhYSeHl$fZOvrPK41uK4pQY^*LS8yPSsk}&}j-@Y^&?Oe?$B(x#U3q z=t9sz>|4U;S3;|~TSck&Uw~*02eB48P>w$%yT_1sH4we!_@*-oJ(vG)j~sBy|NHrW dHUhXACQk{Km|Zex?);~b#}D-HmqMPu`(IUb&=ddw literal 0 HcmV?d00001 diff --git a/docs/assets/readme/open-source-surface.svg b/docs/assets/readme/open-source-surface.svg new file mode 100644 index 0000000..1a5a0cf --- /dev/null +++ b/docs/assets/readme/open-source-surface.svg @@ -0,0 +1,41 @@ + + + + Open Source Surface + 코드만이 아니라 상태, 문서, 릴리즈, 라이브 채널까지 한 화면에서 읽히는 저장소를 목표로 합니다. + + + Code + Desktop + Mobile Web + API + 실제 구현의 중심 + + + Docs + README + Status + Master Plan + 방향과 현실의 정합성 + + + Releases + Forge Releases + Download Host + Checksums + 받아볼 수 있는 결과물 + + + Live + vstalk.phy.kr + Health + Screenshots + 지금 직접 확인 가능한 표면 + + + People + Issues + PRs + Maintainers + 기여와 운영의 접점 + diff --git a/docs/assets/readme/platform-journey.svg b/docs/assets/readme/platform-journey.svg new file mode 100644 index 0000000..f5bc6bf --- /dev/null +++ b/docs/assets/readme/platform-journey.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + Platform Journey + Three product surfaces, one release story: live entrypoint, desktop focus, Android parallel rollout. + + + Mobile Web + vstalk.phy.kr + Lowest friction entrypoint. + Quick signup, short reply, fast re-entry. + + + Windows Desktop + Primary productivity client + Compact shell, list, chat, send, buildable zip. + Targeting search, focus, and multiwindow flow. + + + Android APK + Parallel next channel + Push, repeat use, attachment flow, + and release parity with desktop assets. + + + + + + + + Public release path + Repository docs and screenshots -> Forge Releases -> download-vstalk.phy.kr mirror -> latest links by OS + The project treats release notes, screenshots, and downloadable artifacts as one continuous public surface. + diff --git a/docs/assets/readme/product-pillars.svg b/docs/assets/readme/product-pillars.svg new file mode 100644 index 0000000..e8546d8 --- /dev/null +++ b/docs/assets/readme/product-pillars.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + Product Pillars + A Korean-first messenger project tuned for calm, fast, and transparent communication. + + + + + Korean-first UI + Short copy, low friction, + real Korean usage flow. + Designed around onboarding, read-reply loops, + and recovery language that feel natural. + + + + + + Windows priority + Compact desktop UX with + multiwindow headroom. + The project bets on search, focus tools, + and task-oriented message recovery. + + + + + + Transparent surface + Docs, screenshots, + releases, and status aligned. + This repo treats public project surfaces as + part of the product, not afterthoughts. + + diff --git a/docs/assets/readme/public-contract.svg b/docs/assets/readme/public-contract.svg new file mode 100644 index 0000000..12198c9 --- /dev/null +++ b/docs/assets/readme/public-contract.svg @@ -0,0 +1,26 @@ + + + + Public Contract + 이 저장소가 공개면에서 지키려는 네 가지 약속 + + + Transparent State + README와 상태표는 실제 구현보다 + 좋아 보이도록 과장하지 않는다. + + + Release Discipline + 릴리즈, 스크린샷, 다운로드, + CHANGELOG를 같이 맞춘다. + + + Korean-first UX + 번역체보다 실제 한국어 흐름과 + 낮은 피로도의 사용성을 우선한다. + + + Open Planning + 문서와 비판적 리뷰를 공개해 + 방향과 한계를 숨기지 않는다. + diff --git a/docs/assets/readme/release-flow.svg b/docs/assets/readme/release-flow.svg new file mode 100644 index 0000000..4f870f3 --- /dev/null +++ b/docs/assets/readme/release-flow.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + Release Surface + The repository keeps code, screenshots, release rules, and download surfaces aligned. + + + Build sources + Code + Docs + Screenshots + README + 문서/ planning set + latest repo screenshots + + + Packaging + Release assets + portable zip + apk channel next + checksums and metadata + + + Public surfaces + Forge Releases + versioned assets + release notes + source of record + + + Delivery + Download host + windows/latest + android/latest + version.json + + + + + + + + + + + Principle: what ships must match what the repo says. + diff --git a/docs/assets/readme/system-overview.svg b/docs/assets/readme/system-overview.svg new file mode 100644 index 0000000..0e01351 --- /dev/null +++ b/docs/assets/readme/system-overview.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + System Overview + Windows-first messenger with a live mobile web entrypoint and a transparent release surface. + + + + Windows Desktop + Avalonia 12 + Conversation list, chat view, send flow, + portable build channel. + + + Mobile Web + vstalk.phy.kr + PWA shell, quick signup, list, chat, + same-origin API and WebSocket flow. + + + Android + Next parallel client + + + + + + ASP.NET Core 8 API + Protocols + REST bootstrap, conversations, messages + WebSocket realtime events + + + + + Storage now + SQLite + Alpha storage with simple deployment + and low operational overhead. + + + Operational surface + Caddy + nginx + TLS, same-origin mobile web, + shared VPS reverse proxy routing. + + + Target stack later + PostgreSQL / Redis / MinIO + + + + + + + + + + + + + + + + diff --git a/docs/repository-surfaces.md b/docs/repository-surfaces.md new file mode 100644 index 0000000..116f034 --- /dev/null +++ b/docs/repository-surfaces.md @@ -0,0 +1,29 @@ +# Repository Surfaces + +이 문서는 공개 저장소에서 무엇을 어떤 이름으로 노출하는지 정리합니다. + +## Public Naming + +- 제품 노출명: `KoTalk` +- 한글 표기: `코톡` +- 웹 진입 도메인: `vstalk.phy.kr` +- 다운로드 미러: `download-vstalk.phy.kr` + +## Repository Structure + +- `src/`, `tests/`: 제품 코드와 검증 코드 +- `docs/`: 공개 보조 문서와 시각 자산 +- `문서/`: 제품 마스터 플랜과 UX 아틀라스 +- `deploy/`: 범용 배포 골격 +- `release-assets/`: 릴리즈 메타데이터와 배포 자산 스테이징 + +## Public Writing Rules + +- README는 첫 방문자가 30초 안에 판단할 수 있게 유지합니다. +- 공개 문서에는 실제 운영 힌트, 비밀값, 내부 메모를 적지 않습니다. +- 공식 서비스와 오픈소스 저장소는 같은 표면처럼 쓰지 않습니다. + +## Current Technical Note + +현재 저장소의 코드 네임스페이스와 프로젝트 파일은 아직 `PhysOn.*`를 사용합니다. +이 문서는 공개 브랜드 기준을 먼저 정리한 것이며, 소스 네임스페이스 정렬은 별도 작업입니다. diff --git a/global.json b/global.json new file mode 100644 index 0000000..ec5b69c --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "8.0.420" + } +} \ No newline at end of file diff --git a/release-assets/.gitignore b/release-assets/.gitignore new file mode 100644 index 0000000..4d7416f --- /dev/null +++ b/release-assets/.gitignore @@ -0,0 +1,9 @@ +* +!.gitignore +!README.md +!latest/ +!latest/.gitkeep +!releases/ +!releases/.gitkeep +!templates/ +!templates/RELEASE_NOTES.ko.md diff --git a/release-assets/README.md b/release-assets/README.md new file mode 100644 index 0000000..230aad3 --- /dev/null +++ b/release-assets/README.md @@ -0,0 +1,93 @@ +# Release Assets + +이 디렉터리는 Windows와 Android 클라이언트 산출물을 함께 정리하는 멀티플랫폼 릴리즈 스테이징 영역입니다. 소스 코드 디렉터리가 아니라, 생성된 릴리즈 메타데이터와 배포 번들을 잠시 정리하는 generated surface로 취급합니다. + +## 목표 + +- 같은 버전 번호 아래에 Windows와 Android 산출물을 병렬로 보관합니다. +- `latest/`는 최신 포인터, `releases//`는 불변 이력으로 구분합니다. +- 원격 Forge Releases는 버전별 원본 저장소, `download-vs-messanger.phy.kr`는 최종 사용자용 다운로드 미러로 사용합니다. + +## 목표 구조 + +```text +release-assets/ + latest/ + version.json + latest.json + RELEASE_NOTES.ko.md + SHA256SUMS.txt + screenshots/ + windows/ + VsMessenger-win-x64.zip + SHA256SUMS.txt + version.json + android/ + VsMessenger-android-universal.apk + SHA256SUMS.txt + version.json + releases/ + v0.2.0-alpha.1/ + version.json + RELEASE_NOTES.ko.md + SHA256SUMS.txt + screenshots/ + windows/ + x64/ + VsMessenger-win-x64-v0.2.0-alpha.1.zip + SHA256SUMS.txt + android/ + universal/ + VsMessenger-android-universal-v0.2.0-alpha.1.apk + SHA256SUMS.txt +``` + +## 기본 규칙 + +- 같은 버전은 같은 서버 API 계약과 같은 릴리즈 노트를 공유합니다. +- Windows와 Android는 같은 태그 아래 병렬 산출물로 게시합니다. +- Windows 기본 공개 형식은 `zip`, Android 기본 공개 형식은 `apk`입니다. +- APK는 공개 채널에 올릴 때 반드시 서명본을 사용합니다. +- `latest/version.json`은 전체 플랫폼 상태를 담고, `latest/windows/version.json`, `latest/android/version.json`은 플랫폼별 상세 포인터를 담습니다. + +## 다운로드 경로 규칙 + +- 최신 Windows: `https://download-vs-messanger.phy.kr/windows/latest` +- 최신 Android: `https://download-vs-messanger.phy.kr/android/latest` +- 전체 최신 메타데이터: `https://download-vs-messanger.phy.kr/latest/version.json` +- 버전별 Windows: `https://download-vs-messanger.phy.kr/releases//windows/x64/...` +- 버전별 Android: `https://download-vs-messanger.phy.kr/releases//android/universal/...` + +## 생성 스크립트 + +실제 파일 생성은 `scripts/release/release-prepare-assets.sh`를 사용합니다. + +예시: + +```bash +./scripts/release/release-prepare-assets.sh \ + --version v0.2.0-alpha.1 \ + --channel alpha \ + --windows-zip artifacts/release/VsMessenger-win-x64-v0.2.0-alpha.1.zip \ + --android-apk artifacts/release/VsMessenger-android-universal-v0.2.0-alpha.1.apk \ + --screenshots artifacts/screenshots \ + --force +``` + +## 업로드 스크립트 + +- VPS 다운로드 미러 업로드: `scripts/release/release-upload-assets.sh` +- Forge Releases 게시: `scripts/release/release-publish-forge.sh` + +두 채널은 목적이 다릅니다. + +- Forge Releases: 버전별 원본 보관 +- 다운로드 미러: 최신 포인터와 빠른 정적 다운로드 +- 모바일 웹앱: `release-assets/`가 아니라 `vstalk.phy.kr` 배포 트랙에서 별도 운영 + +## 운영 메모 + +- 생성된 버전별 산출물은 기본적으로 Git 추적 대상이 아닙니다. +- 공개 릴리즈마다 `RELEASE_NOTES.ko.md`, `SHA256SUMS.txt`, `version.json`을 함께 갱신합니다. +- 같은 버전에서 Windows만 있고 Android가 아직 없을 수는 있지만, 장기 원칙은 `같은 버전 아래 두 플랫폼 병렬 게시`입니다. +- 모바일 웹앱 정적 산출물은 `release-assets/`가 아니라 `/srv/vs-messanger/webapp/releases/`에 배포합니다. diff --git a/release-assets/latest/.gitkeep b/release-assets/latest/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/release-assets/releases/.gitkeep b/release-assets/releases/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/release-assets/releases/.gitkeep @@ -0,0 +1 @@ + diff --git a/release-assets/templates/RELEASE_NOTES.ko.md b/release-assets/templates/RELEASE_NOTES.ko.md new file mode 100644 index 0000000..3cc1c47 --- /dev/null +++ b/release-assets/templates/RELEASE_NOTES.ko.md @@ -0,0 +1,19 @@ +# vs-messanger {{VERSION}} + +- 채널: `{{CHANNEL}}` +- 게시 시각: `{{PUBLISHED_AT}}` + +## 이번 빌드에 포함된 것 + +- Windows x64 portable zip +- Android universal apk +- 무결성 체크용 `SHA256SUMS.txt` +- 버전 메타데이터 `version.json`, `latest.json` +- 필요 시 한국어 스크린샷 + +## 확인할 것 + +- 가입 진입 +- 첫 대화 진입 +- 메시지 송신/수신 +- 다운로드 링크 동작 diff --git a/scripts/capture_vstalk_web_screenshots.cjs b/scripts/capture_vstalk_web_screenshots.cjs new file mode 100644 index 0000000..7f1113e --- /dev/null +++ b/scripts/capture_vstalk_web_screenshots.cjs @@ -0,0 +1 @@ +require('./ci/capture-vstalk-web-screenshots.cjs') diff --git a/scripts/ci/capture-vstalk-web-screenshots.cjs b/scripts/ci/capture-vstalk-web-screenshots.cjs new file mode 100644 index 0000000..0c27692 --- /dev/null +++ b/scripts/ci/capture-vstalk-web-screenshots.cjs @@ -0,0 +1,397 @@ +const fs = require('node:fs/promises') +const path = require('node:path') +const process = require('node:process') +const { createRequire } = require('node:module') + +const webPackageRequire = createRequire(path.resolve(process.cwd(), 'src/PhysOn.Web/package.json')) +const puppeteer = webPackageRequire('puppeteer-core') + +const baseUrl = process.env.VSTALK_CAPTURE_URL ?? 'http://127.0.0.1:4174/' +const outputDir = process.env.VSTALK_CAPTURE_OUTPUT_DIR + ?? path.resolve(process.cwd(), 'docs/assets/latest') +const executablePath = process.env.CHROME_BIN ?? '/usr/bin/google-chrome' + +const bootstrapPayload = { + me: { + userId: 'me-1', + displayName: '이안', + profileImageUrl: null, + statusMessage: '업무와 일상을 가볍게 잇는 중', + }, + session: { + sessionId: 'session-alpha-web', + deviceId: 'device-web-alpha', + deviceName: 'Mobile Web', + createdAt: '2026-04-16T04:50:00Z', + }, + ws: { + url: 'wss://vstalk.phy.kr/v1/realtime/ws', + }, + conversations: { + items: [ + { + conversationId: 'conv-team', + type: 'group', + title: '제품 운영', + avatarUrl: null, + subtitle: '10시 전에 공유안만 확인해 주세요.', + memberCount: 4, + isMuted: false, + isPinned: true, + sortKey: '2026-04-16T05:04:00Z', + unreadCount: 2, + lastReadSequence: 10, + lastMessage: { + messageId: 'msg-11', + text: '10시 전에 공유안만 확인해 주세요.', + createdAt: '2026-04-16T05:04:00Z', + senderUserId: 'u-2', + }, + }, + { + conversationId: 'conv-friends', + type: 'group', + title: '주말 약속', + avatarUrl: null, + subtitle: '토요일 2시에 브런치 어때?', + memberCount: 3, + isMuted: false, + isPinned: false, + sortKey: '2026-04-16T04:48:00Z', + unreadCount: 0, + lastReadSequence: 5, + lastMessage: { + messageId: 'msg-22', + text: '토요일 2시에 브런치 어때?', + createdAt: '2026-04-16T04:48:00Z', + senderUserId: 'u-3', + }, + }, + ], + nextCursor: null, + }, +} + +const messageMap = { + 'conv-team': { + items: [ + { + messageId: 'msg-8', + conversationId: 'conv-team', + clientMessageId: 'client-8', + kind: 'text', + text: '회의 전에 이슈만 짧게 정리해 주세요.', + createdAt: '2026-04-16T04:40:00Z', + editedAt: null, + sender: { + userId: 'u-2', + displayName: '민지', + profileImageUrl: null, + }, + isMine: false, + serverSequence: 8, + }, + { + messageId: 'msg-9', + conversationId: 'conv-team', + clientMessageId: 'client-9', + kind: 'text', + text: '공유안은 정리해 두었습니다. 바로 올릴게요.', + createdAt: '2026-04-16T04:47:00Z', + editedAt: null, + sender: { + userId: 'me-1', + displayName: '이안', + profileImageUrl: null, + }, + isMine: true, + serverSequence: 9, + }, + { + messageId: 'msg-10', + conversationId: 'conv-team', + clientMessageId: 'client-10', + kind: 'text', + text: '좋아요. 10시 전에 공유안만 확인해 주세요.', + createdAt: '2026-04-16T05:04:00Z', + editedAt: null, + sender: { + userId: 'u-2', + displayName: '민지', + profileImageUrl: null, + }, + isMine: false, + serverSequence: 10, + }, + { + messageId: 'msg-11', + conversationId: 'conv-team', + clientMessageId: 'client-11', + kind: 'text', + text: '10시 전에 공유안만 확인해 주세요.', + createdAt: '2026-04-16T05:04:00Z', + editedAt: null, + sender: { + userId: 'u-2', + displayName: '민지', + profileImageUrl: null, + }, + isMine: false, + serverSequence: 11, + }, + ], + nextCursor: null, + }, + 'conv-friends': { + items: [ + { + messageId: 'msg-20', + conversationId: 'conv-friends', + clientMessageId: 'client-20', + kind: 'text', + text: '이번 주말에 시간 괜찮아?', + createdAt: '2026-04-16T04:32:00Z', + editedAt: null, + sender: { + userId: 'u-3', + displayName: '수아', + profileImageUrl: null, + }, + isMine: false, + serverSequence: 4, + }, + { + messageId: 'msg-21', + conversationId: 'conv-friends', + clientMessageId: 'client-21', + kind: 'text', + text: '토요일 2시에 브런치 어때?', + createdAt: '2026-04-16T04:48:00Z', + editedAt: null, + sender: { + userId: 'u-3', + displayName: '수아', + profileImageUrl: null, + }, + isMine: false, + serverSequence: 5, + }, + ], + nextCursor: null, + }, +} + +const storedSession = { + apiBaseUrl: '', + tokens: { + accessToken: 'access-token-alpha', + accessTokenExpiresAt: '2026-04-16T06:00:00Z', + refreshToken: 'refresh-token-alpha', + refreshTokenExpiresAt: '2026-05-16T06:00:00Z', + }, + bootstrap: bootstrapPayload, + savedAt: '2026-04-16T05:04:00Z', +} + +async function ensureOutputDir() { + await fs.mkdir(outputDir, { recursive: true }) +} + +async function createBrowser() { + return puppeteer.launch({ + executablePath, + headless: 'new', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + defaultViewport: { + width: 390, + height: 844, + isMobile: true, + hasTouch: true, + deviceScaleFactor: 2, + }, + }) +} + +async function installSessionMocks(page) { + await page.evaluateOnNewDocument((session) => { + class FakeWebSocket { + constructor(url) { + this.url = url + this.readyState = 0 + this.onopen = null + this.onclose = null + this.onerror = null + this.onmessage = null + window.setTimeout(() => { + this.readyState = 1 + if (this.onopen) { + this.onopen({ type: 'open' }) + } + }, 80) + } + + close() { + this.readyState = 3 + if (this.onclose) { + this.onclose({ type: 'close' }) + } + } + + send() {} + } + + window.localStorage.setItem('vs-talk.session', JSON.stringify(session)) + window.localStorage.setItem('vs-talk.invite-code', 'ALPHA') + window.WebSocket = FakeWebSocket + }, storedSession) + + await page.setRequestInterception(true) + page.on('request', (request) => { + const url = new URL(request.url()) + + if (url.pathname === '/v1/bootstrap') { + request.respond({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ data: bootstrapPayload }), + }) + return + } + + if (/\/v1\/conversations\/[^/]+\/messages$/.test(url.pathname)) { + const match = url.pathname.match(/\/v1\/conversations\/([^/]+)\/messages/) + const conversationId = match ? match[1] : '' + const payload = messageMap[conversationId] ?? { items: [], nextCursor: null } + + request.respond({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ data: payload }), + }) + return + } + + if (/\/v1\/conversations\/[^/]+\/read-cursor$/.test(url.pathname)) { + const match = url.pathname.match(/\/v1\/conversations\/([^/]+)\/read-cursor/) + const conversationId = match ? match[1] : '' + const body = JSON.parse(request.postData() ?? '{}') + + request.respond({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + data: { + conversationId, + accountId: 'me-1', + lastReadSequence: body.lastReadSequence ?? 0, + updatedAt: '2026-04-16T05:05:00Z', + }, + }), + }) + return + } + + if (url.pathname === '/v1/auth/token/refresh') { + request.respond({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + data: { + tokens: storedSession.tokens, + }, + }), + }) + return + } + + request.continue() + }) +} + +async function captureOnboarding(browser) { + const page = await browser.newPage() + await page.goto(baseUrl, { waitUntil: 'networkidle2' }) + await page.evaluate(() => { + window.localStorage.clear() + }) + await page.reload({ waitUntil: 'networkidle2' }) + await page.screenshot({ + path: path.join(outputDir, 'vstalk-web-onboarding.png'), + fullPage: false, + }) + await page.close() +} + +async function captureConversationList(browser) { + const page = await browser.newPage() + await installSessionMocks(page) + await page.goto(baseUrl, { waitUntil: 'networkidle2' }) + await page.waitForSelector('.conversation-row') + await page.screenshot({ + path: path.join(outputDir, 'vstalk-web-list.png'), + fullPage: false, + }) + await page.close() +} + +async function captureConversation(browser) { + const page = await browser.newPage() + await installSessionMocks(page) + await page.goto(baseUrl, { waitUntil: 'networkidle2' }) + await page.waitForSelector('.conversation-row') + await page.click('.conversation-row') + await page.waitForSelector('.message-bubble') + await page.screenshot({ + path: path.join(outputDir, 'vstalk-web-chat.png'), + fullPage: false, + }) + await page.close() +} + +async function captureSearch(browser) { + const page = await browser.newPage() + await installSessionMocks(page) + await page.goto(baseUrl, { waitUntil: 'networkidle2' }) + await page.waitForSelector('.bottom-bar') + await page.click('.bottom-bar .nav-button:nth-child(2)') + await page.waitForSelector('.search-field') + await page.screenshot({ + path: path.join(outputDir, 'vstalk-web-search.png'), + fullPage: false, + }) + await page.close() +} + +async function captureSaved(browser) { + const page = await browser.newPage() + await installSessionMocks(page) + await page.goto(baseUrl, { waitUntil: 'networkidle2' }) + await page.waitForSelector('.bottom-bar') + await page.click('.bottom-bar .nav-button:nth-child(3)') + await page.waitForSelector('.saved-section') + await page.screenshot({ + path: path.join(outputDir, 'vstalk-web-saved.png'), + fullPage: false, + }) + await page.close() +} + +async function main() { + await ensureOutputDir() + const browser = await createBrowser() + + try { + await captureOnboarding(browser) + await captureConversationList(browser) + await captureSearch(browser) + await captureSaved(browser) + await captureConversation(browser) + } finally { + await browser.close() + } +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) diff --git a/scripts/deploy-mvp-stack.sh b/scripts/deploy-mvp-stack.sh new file mode 100755 index 0000000..4c67347 --- /dev/null +++ b/scripts/deploy-mvp-stack.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/deploy/deploy-stack-mvp.sh" "$@" diff --git a/scripts/deploy-webapp.sh b/scripts/deploy-webapp.sh new file mode 100755 index 0000000..156f076 --- /dev/null +++ b/scripts/deploy-webapp.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/deploy/deploy-webapp-static.sh" "$@" diff --git a/scripts/deploy/deploy-stack-mvp.sh b/scripts/deploy/deploy-stack-mvp.sh new file mode 100755 index 0000000..3059b50 --- /dev/null +++ b/scripts/deploy/deploy-stack-mvp.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/deploy/deploy-stack-mvp.sh --host example.com --user deploy [options] + +Options: + --app-dir Remote application root. Default: /srv/vs-messanger/app + --download-root Remote download root. Default: /srv/vs-messanger/download + --ssh-key Private key used for SSH/rsync + --dry-run Print the rsync plan without changing the server + +Notes: + - Remote host must already contain a valid deploy/.env file. + - This script syncs deploy files and the current src tree, then runs docker compose. +EOF +} + +host="" +user="" +app_dir="/srv/vs-messanger/app" +download_root="/srv/vs-messanger/download" +ssh_key="" +dry_run="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) + host="${2:-}" + shift 2 + ;; + --user) + user="${2:-}" + shift 2 + ;; + --app-dir) + app_dir="${2:-}" + shift 2 + ;; + --download-root) + download_root="${2:-}" + shift 2 + ;; + --ssh-key) + ssh_key="${2:-}" + shift 2 + ;; + --dry-run) + dry_run="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$host" || -z "$user" ]]; then + usage >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +ssh_cmd=(ssh -o StrictHostKeyChecking=accept-new) +if [[ -n "$ssh_key" ]]; then + ssh_cmd+=(-i "$ssh_key") +fi + +rsync_opts=(-az) +if [[ "$dry_run" == "true" ]]; then + rsync_opts+=(--dry-run) +fi + +target_host="$user@$host" +rsh="${ssh_cmd[*]}" + +"${ssh_cmd[@]}" "$target_host" "mkdir -p '$app_dir' '$download_root'" + +rsync "${rsync_opts[@]}" \ + -e "$rsh" \ + --delete \ + --filter="protect /deploy/.env" \ + --include "/VsMessenger.sln" \ + --include "/global.json" \ + --include "/deploy/***" \ + --include "/src/***" \ + --exclude "*" \ + "$repo_root"/ "$target_host:$app_dir/" + +if [[ "$dry_run" == "true" ]]; then + echo "Dry run complete. Remote compose was not started." + exit 0 +fi + +"${ssh_cmd[@]}" "$target_host" \ + "cd '$app_dir' && test -f deploy/.env && docker compose --env-file deploy/.env -f deploy/compose.mvp.yml up -d --build --remove-orphans" + +echo "Deployed MVP stack to $target_host:$app_dir" diff --git a/scripts/deploy/deploy-webapp-static.sh b/scripts/deploy/deploy-webapp-static.sh new file mode 100755 index 0000000..a7b5815 --- /dev/null +++ b/scripts/deploy/deploy-webapp-static.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/deploy/deploy-webapp-static.sh --host example.com --user deploy --source dist [options] + +Options: + --source Local webapp build directory to upload + --version Remote release directory name. Default: current timestamp + --app-dir Remote application root. Default: /srv/vs-messanger/app + --target Remote webapp root. Default: /srv/vs-messanger/webapp + --ssh-key Private key used for SSH/rsync + --dry-run Print the rsync plan without changing the server + +Notes: + - Remote host must already contain a valid deploy/.env file. + - This script uploads static webapp files into releases/ and repoints current -> releases/. + - The webapp is expected to be served at https://vstalk.phy.kr via Caddy + compose.webapp.yml. +EOF +} + +host="" +user="" +source_dir="" +version="$(date +%Y%m%d-%H%M%S)" +app_dir="/srv/vs-messanger/app" +target="/srv/vs-messanger/webapp" +ssh_key="" +dry_run="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) + host="${2:-}" + shift 2 + ;; + --user) + user="${2:-}" + shift 2 + ;; + --source) + source_dir="${2:-}" + shift 2 + ;; + --version) + version="${2:-}" + shift 2 + ;; + --app-dir) + app_dir="${2:-}" + shift 2 + ;; + --target) + target="${2:-}" + shift 2 + ;; + --ssh-key) + ssh_key="${2:-}" + shift 2 + ;; + --dry-run) + dry_run="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$host" || -z "$user" || -z "$source_dir" ]]; then + usage >&2 + exit 1 +fi + +if [[ ! -d "$source_dir" ]]; then + echo "Source directory not found: $source_dir" >&2 + exit 1 +fi + +ssh_cmd=(ssh -o StrictHostKeyChecking=accept-new) +if [[ -n "$ssh_key" ]]; then + ssh_cmd+=(-i "$ssh_key") +fi + +rsync_opts=(-az) +if [[ "$dry_run" == "true" ]]; then + rsync_opts+=(--dry-run) +else + rsync_opts+=(--delete) +fi + +target_host="$user@$host" +rsh="${ssh_cmd[*]}" +release_dir="$target/releases/$version" +current_link="$target/current" + +"${ssh_cmd[@]}" "$target_host" "mkdir -p '$release_dir' '$target/releases' '$app_dir'" +rsync "${rsync_opts[@]}" -e "$rsh" "$source_dir"/ "$target_host:$release_dir/" + +if [[ "$dry_run" == "true" ]]; then + echo "Dry run complete. Remote symlink and compose were not updated." + exit 0 +fi + +"${ssh_cmd[@]}" "$target_host" \ + "ln -sfn '$release_dir' '$current_link' && cd '$app_dir' && test -f deploy/.env && docker compose --env-file deploy/.env -f deploy/compose.mvp.yml -f deploy/compose.webapp.yml up -d webapp caddy" + +echo "Deployed webapp release $version to $target_host:$release_dir" diff --git a/scripts/prepare-release-assets.sh b/scripts/prepare-release-assets.sh new file mode 100755 index 0000000..edd0f23 --- /dev/null +++ b/scripts/prepare-release-assets.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/release/release-prepare-assets.sh" "$@" diff --git a/scripts/publish-gitea-release.sh b/scripts/publish-gitea-release.sh new file mode 100755 index 0000000..11eb28e --- /dev/null +++ b/scripts/publish-gitea-release.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/release/release-publish-forge.sh" "$@" diff --git a/scripts/release/release-prepare-assets.sh b/scripts/release/release-prepare-assets.sh new file mode 100755 index 0000000..4a96b92 --- /dev/null +++ b/scripts/release/release-prepare-assets.sh @@ -0,0 +1,329 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/release/release-prepare-assets.sh --version v0.2.0-alpha.1 [options] + +Options: + --windows-zip Windows x64 ZIP artifact path + --android-apk Android universal APK artifact path + --zip Backward-compatible alias for --windows-zip + --channel Release channel. Default: alpha + --notes Existing Korean release notes file + --screenshots

Directory containing *.png/jpg screenshots + --force Overwrite an existing release folder + +Environment: + DOWNLOAD_BASE_URL Defaults to https://download-vs-messanger.phy.kr +EOF +} + +version="" +channel="alpha" +windows_zip="" +android_apk="" +notes_path="" +screenshots_dir="" +force="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + version="${2:-}" + shift 2 + ;; + --channel) + channel="${2:-}" + shift 2 + ;; + --windows-zip|--zip) + windows_zip="${2:-}" + shift 2 + ;; + --android-apk) + android_apk="${2:-}" + shift 2 + ;; + --notes) + notes_path="${2:-}" + shift 2 + ;; + --screenshots) + screenshots_dir="${2:-}" + shift 2 + ;; + --force) + force="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$version" ]]; then + usage >&2 + exit 1 +fi + +if [[ -z "$windows_zip" && -z "$android_apk" ]]; then + echo "At least one artifact must be provided: --windows-zip or --android-apk" >&2 + usage >&2 + exit 1 +fi + +if [[ -n "$windows_zip" && ! -f "$windows_zip" ]]; then + echo "Windows ZIP artifact not found: $windows_zip" >&2 + exit 1 +fi + +if [[ -n "$android_apk" && ! -f "$android_apk" ]]; then + echo "Android APK artifact not found: $android_apk" >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +release_root="$repo_root/release-assets/releases/$version" +latest_root="$repo_root/release-assets/latest" +template_path="$repo_root/release-assets/templates/RELEASE_NOTES.ko.md" +download_base_url="${DOWNLOAD_BASE_URL:-https://download-vs-messanger.phy.kr}" +published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" + +derive_release_url() { + local origin_url + origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)" + + if [[ -z "$origin_url" ]]; then + return 0 + fi + + if [[ "$origin_url" =~ ^https?:// ]]; then + printf '%s/releases/tag/%s' "${origin_url%.git}" "$version" + return 0 + fi + + if [[ "$origin_url" =~ ^git@([^:]+):(.+)\.git$ ]]; then + printf 'https://%s/%s/releases/tag/%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "$version" + return 0 + fi +} + +release_url="$(derive_release_url)" + +if [[ -e "$release_root" && "$force" != "true" ]]; then + echo "Release directory already exists: $release_root" >&2 + echo "Use --force to replace it." >&2 + exit 1 +fi + +rm -rf "$release_root" "$latest_root" +mkdir -p "$release_root/screenshots" "$latest_root/screenshots" + +if [[ -n "$notes_path" ]]; then + cp "$notes_path" "$release_root/RELEASE_NOTES.ko.md" +else + sed \ + -e "s/{{VERSION}}/$version/g" \ + -e "s/{{CHANNEL}}/$channel/g" \ + -e "s/{{PUBLISHED_AT}}/$published_at/g" \ + "$template_path" > "$release_root/RELEASE_NOTES.ko.md" +fi +cp "$release_root/RELEASE_NOTES.ko.md" "$latest_root/RELEASE_NOTES.ko.md" + +if [[ -n "$screenshots_dir" ]]; then + while IFS= read -r screenshot; do + cp "$screenshot" "$release_root/screenshots/$(basename "$screenshot")" + cp "$screenshot" "$latest_root/screenshots/$(basename "$screenshot")" + done < <(find "$screenshots_dir" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort) +fi + +platform_count=0 +platforms_json="" +top_level_windows_alias="" +release_hash_paths=() +latest_hash_paths=() + +append_platform_json() { + local body="$1" + if (( platform_count > 0 )); then + platforms_json+=$',\n' + fi + platforms_json+="$body" + platform_count=$((platform_count + 1)) +} + +write_platform_version_json() { + local path="$1" + local body="$2" + cat > "$path" < SHA256SUMS.txt + ) + + ( + cd "$windows_latest_dir" + sha256sum "$windows_latest_name" > SHA256SUMS.txt + ) + + release_hash_paths+=("windows/x64/$windows_release_name") + latest_hash_paths+=("windows/$windows_latest_name") + + windows_platform_body="$(cat < SHA256SUMS.txt + ) + + ( + cd "$android_latest_dir" + sha256sum "$android_latest_name" > SHA256SUMS.txt + ) + + release_hash_paths+=("android/universal/$android_release_name") + latest_hash_paths+=("android/$android_latest_name") + + android_platform_body="$(cat < 0 )); then + ( + cd "$release_root" + sha256sum "${release_hash_paths[@]}" > SHA256SUMS.txt + ) +fi + +if (( ${#latest_hash_paths[@]} > 0 )); then + ( + cd "$latest_root" + sha256sum "${latest_hash_paths[@]}" > SHA256SUMS.txt + ) +fi + +mapfile -t screenshot_files < <(find "$release_root/screenshots" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort) + +screenshots_json="[]" +if [[ ${#screenshot_files[@]} -gt 0 ]]; then + screenshots_json=$( + for idx in "${!screenshot_files[@]}"; do + name="$(basename "${screenshot_files[$idx]}")" + printf ' "%s/releases/%s/screenshots/%s"' "$download_base_url" "$version" "$name" + if (( idx < ${#screenshot_files[@]} - 1 )); then + printf ',\n' + else + printf '\n' + fi + done + ) + screenshots_json="[ +$screenshots_json + ]" +fi + +cat > "$release_root/version.json" < Forge base URL. Example: https://forge.example.com + --repo Repository in owner/name form + --token Gitea API token + --dry-run Print planned uploads without calling the API +EOF +} + +version="" +base_url="" +repo_full_name="" +token="${FORGE_RELEASE_TOKEN:-${GITEA_RELEASE_TOKEN:-}}" +dry_run="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + version="${2:-}" + shift 2 + ;; + --base-url) + base_url="${2:-}" + shift 2 + ;; + --repo) + repo_full_name="${2:-}" + shift 2 + ;; + --token) + token="${2:-}" + shift 2 + ;; + --dry-run) + dry_run="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$version" ]]; then + usage >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +release_root="$repo_root/release-assets/releases/$version" + +if [[ ! -d "$release_root" ]]; then + echo "Release bundle not found: $release_root" >&2 + exit 1 +fi + +origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)" + +if [[ -z "$base_url" ]]; then + if [[ "$origin_url" =~ ^(https?://[^/]+)/(.+)\.git$ ]]; then + base_url="${BASH_REMATCH[1]}" + elif [[ "$origin_url" =~ ^git@([^:]+):(.+)\.git$ ]]; then + base_url="https://${BASH_REMATCH[1]}" + fi +fi + +if [[ -z "$repo_full_name" ]]; then + if [[ "$origin_url" =~ ^https?://[^/]+/(.+)\.git$ ]]; then + repo_full_name="${BASH_REMATCH[1]}" + elif [[ "$origin_url" =~ ^git@[^:]+:(.+)\.git$ ]]; then + repo_full_name="${BASH_REMATCH[1]}" + fi +fi + +if [[ -z "$base_url" || -z "$repo_full_name" ]]; then + echo "Unable to infer forge base URL or repository name from origin remote." >&2 + exit 1 +fi + +if [[ "$dry_run" != "true" && -z "$token" ]]; then + echo "A Gitea API token is required. Use --token or FORGE_RELEASE_TOKEN." >&2 + exit 1 +fi + +api_root="${base_url%/}/api/v1/repos/${repo_full_name}" +release_api="${api_root}/releases" +tag_api="${release_api}/tags/${version}" +pre_release="false" + +case "$version" in + *alpha*|*beta*|*rc*) + pre_release="true" + ;; +esac + +mapfile -t asset_files < <( + find "$release_root" -type f \ + \( -name '*.zip' -o -name '*.apk' -o -name 'RELEASE_NOTES.ko.md' -o -name 'SHA256SUMS.txt' -o -name 'version.json' \) \ + | sort +) + +if [[ ${#asset_files[@]} -eq 0 ]]; then + echo "No release assets found in $release_root" >&2 + exit 1 +fi + +echo "Forge release target: $base_url/$repo_full_name" +echo "Version: $version" +printf 'Assets:\n' +printf ' - %s\n' "${asset_files[@]#$release_root/}" + +if [[ "$dry_run" == "true" ]]; then + exit 0 +fi + +auth_header="Authorization: token $token" +tmp_response="$(mktemp)" +trap 'rm -f "$tmp_response"' EXIT + +existing_status="$(curl -sS -o "$tmp_response" -w '%{http_code}' -H "$auth_header" "$tag_api")" +if [[ "$existing_status" == "200" ]]; then + existing_release_id="$(python3 - <<'PY' "$tmp_response" +import json, sys +with open(sys.argv[1], "r", encoding="utf-8") as fh: + print(json.load(fh)["id"]) +PY +)" + curl -sS -X DELETE -H "$auth_header" "${release_api}/${existing_release_id}" >/dev/null +fi + +release_body=$'Windows와 Android 클라이언트 산출물을 병렬로 정리한 릴리즈입니다.\n\n' +release_body+=$'동일 버전 번호 아래 OS별 자산을 함께 게시하며, 최신 다운로드 채널은 download-vs-messanger.phy.kr에서 운영합니다.' + +create_payload="$(python3 - <<'PY' "$version" "$release_body" "$pre_release" +import json, sys +version, body, prerelease = sys.argv[1], sys.argv[2], sys.argv[3] == "true" +print(json.dumps({ + "tag_name": version, + "name": version, + "body": body, + "draft": False, + "prerelease": prerelease +}, ensure_ascii=False)) +PY +)" + +create_status="$(curl -sS -o "$tmp_response" -w '%{http_code}' \ + -X POST \ + -H "$auth_header" \ + -H 'Content-Type: application/json' \ + -d "$create_payload" \ + "$release_api")" + +if [[ "$create_status" != "201" ]]; then + echo "Failed to create forge release. HTTP $create_status" >&2 + cat "$tmp_response" >&2 + exit 1 +fi + +release_id="$(python3 - <<'PY' "$tmp_response" +import json, sys +with open(sys.argv[1], "r", encoding="utf-8") as fh: + print(json.load(fh)["id"]) +PY +)" + +for asset in "${asset_files[@]}"; do + name="$(basename "$asset")" + curl -sS \ + -X POST \ + -H "$auth_header" \ + -H 'Content-Type: application/octet-stream' \ + --data-binary @"$asset" \ + "${release_api}/${release_id}/assets?name=${name}" >/dev/null +done + +echo "Published forge release $version with ${#asset_files[@]} assets." diff --git a/scripts/release/release-upload-assets.sh b/scripts/release/release-upload-assets.sh new file mode 100755 index 0000000..3cb205d --- /dev/null +++ b/scripts/release/release-upload-assets.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/release/release-upload-assets.sh --version v0.2.0-alpha.1 --host example.com --user deploy [options] + +Options: + --target Remote download root. Default: /srv/vs-messanger/download + --ssh-key Private key used for SSH/rsync + --dry-run Print the rsync plan without changing the server +EOF +} + +version="" +host="" +user="" +target="/srv/vs-messanger/download" +ssh_key="" +dry_run="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + version="${2:-}" + shift 2 + ;; + --host) + host="${2:-}" + shift 2 + ;; + --user) + user="${2:-}" + shift 2 + ;; + --target) + target="${2:-}" + shift 2 + ;; + --ssh-key) + ssh_key="${2:-}" + shift 2 + ;; + --dry-run) + dry_run="true" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +if [[ -z "$version" || -z "$host" || -z "$user" ]]; then + usage >&2 + exit 1 +fi + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +release_root="$repo_root/release-assets/releases/$version" +latest_root="$repo_root/release-assets/latest" + +if [[ ! -d "$release_root" || ! -d "$latest_root" ]]; then + echo "Prepared release bundle not found for version $version" >&2 + exit 1 +fi + +ssh_cmd=(ssh -o StrictHostKeyChecking=accept-new) +if [[ -n "$ssh_key" ]]; then + ssh_cmd+=(-i "$ssh_key") +fi + +rsync_opts=(-az) +if [[ "$dry_run" == "true" ]]; then + rsync_opts+=(--dry-run) +else + rsync_opts+=(--delete) +fi + +target_host="$user@$host" +rsh="${ssh_cmd[*]}" + +"${ssh_cmd[@]}" "$target_host" "mkdir -p '$target/releases/$version' '$target/latest' '$target/windows/latest' '$target/android/latest'" +rsync "${rsync_opts[@]}" -e "$rsh" "$release_root"/ "$target_host:$target/releases/$version/" +rsync "${rsync_opts[@]}" -e "$rsh" "$latest_root"/ "$target_host:$target/latest/" +if [[ -d "$latest_root/windows" ]]; then + rsync "${rsync_opts[@]}" -e "$rsh" "$latest_root/windows/" "$target_host:$target/windows/latest/" +fi +if [[ -d "$latest_root/android" ]]; then + rsync "${rsync_opts[@]}" -e "$rsh" "$latest_root/android/" "$target_host:$target/android/latest/" +fi + +echo "Uploaded release assets for $version to $target_host:$target" diff --git a/scripts/upload-release-assets.sh b/scripts/upload-release-assets.sh new file mode 100755 index 0000000..739fb1f --- /dev/null +++ b/scripts/upload-release-assets.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$script_dir/release/release-upload-assets.sh" "$@" diff --git a/src/PhysOn.Api/Auth/ClaimsPrincipalExtensions.cs b/src/PhysOn.Api/Auth/ClaimsPrincipalExtensions.cs new file mode 100644 index 0000000..18cc8f7 --- /dev/null +++ b/src/PhysOn.Api/Auth/ClaimsPrincipalExtensions.cs @@ -0,0 +1,37 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using PhysOn.Application.Exceptions; + +namespace PhysOn.Api.Auth; + +public static class ClaimsPrincipalExtensions +{ + public static Guid RequireAccountId(this ClaimsPrincipal principal) + { + var raw = principal.FindFirstValue(JwtRegisteredClaimNames.Sub) + ?? principal.FindFirstValue(ClaimTypes.NameIdentifier); + return ParseGuid(raw, "invalid_account_claim"); + } + + public static Guid RequireSessionId(this ClaimsPrincipal principal) + { + var raw = principal.FindFirstValue("sid"); + return ParseGuid(raw, "invalid_session_claim"); + } + + public static Guid RequireDeviceId(this ClaimsPrincipal principal) + { + var raw = principal.FindFirstValue("did"); + return ParseGuid(raw, "invalid_device_claim"); + } + + private static Guid ParseGuid(string? raw, string code) + { + if (Guid.TryParse(raw, out var value)) + { + return value; + } + + throw new AppException(code, "인증 정보가 올바르지 않습니다.", System.Net.HttpStatusCode.Unauthorized); + } +} diff --git a/src/PhysOn.Api/Endpoints/MessengerEndpoints.cs b/src/PhysOn.Api/Endpoints/MessengerEndpoints.cs new file mode 100644 index 0000000..b7c4ee7 --- /dev/null +++ b/src/PhysOn.Api/Endpoints/MessengerEndpoints.cs @@ -0,0 +1,310 @@ +using System.Net.WebSockets; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using Microsoft.EntityFrameworkCore; +using PhysOn.Api.Auth; +using PhysOn.Application.Abstractions; +using PhysOn.Application.Exceptions; +using PhysOn.Application.Services; +using PhysOn.Contracts.Auth; +using PhysOn.Contracts.Common; +using PhysOn.Contracts.Conversations; +using PhysOn.Infrastructure.Realtime; + +namespace PhysOn.Api.Endpoints; + +public static class MessengerEndpoints +{ + public static IEndpointRouteBuilder MapPhysOnEndpoints(this IEndpointRouteBuilder endpoints) + { + endpoints.MapPost( + "/v1/auth/register/alpha-quick", + async ( + RegisterAlphaQuickRequest request, + MessengerApplicationService service, + HttpContext httpContext, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var wsUrl = BuildWsUrl(httpContext); + var response = await service.RegisterAlphaQuickAsync(request, wsUrl, cancellationToken); + return Results.Ok(new ApiEnvelope(response)); + }) + .RequireRateLimiting("auth"); + + endpoints.MapPost( + "/v1/auth/token/refresh", + async ( + RefreshTokenRequest request, + HttpContext httpContext, + IAppDbContext db, + IClock clock, + ITokenService tokenService, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var refreshToken = request.RefreshToken?.Trim(); + if (string.IsNullOrWhiteSpace(refreshToken)) + { + throw SessionExpired(); + } + + var now = clock.UtcNow; + var refreshTokenHash = HashRefreshToken(refreshToken); + var session = await db.Sessions + .Include(x => x.Account) + .Include(x => x.Device) + .FirstOrDefaultAsync(x => x.RefreshTokenHash == refreshTokenHash, cancellationToken); + + if (session is null) + { + throw SessionExpired(); + } + + if (session.RevokedAt is not null) + { + throw SessionRevoked(); + } + + if (!session.IsActive(now)) + { + session.RevokedAt = now; + await db.SaveChangesAsync(cancellationToken); + throw SessionExpired(); + } + + if (session.Account is null || session.Device is null) + { + session.RevokedAt = now; + await db.SaveChangesAsync(cancellationToken); + throw SessionRevoked(); + } + + var issuedTokens = tokenService.IssueTokens(session.Account, session, session.Device, now); + session.RefreshTokenHash = issuedTokens.RefreshTokenHash; + session.ExpiresAt = issuedTokens.RefreshTokenExpiresAt; + session.LastSeenAt = now; + session.Device.LastSeenAt = now; + + await db.SaveChangesAsync(cancellationToken); + + return Results.Ok( + new ApiEnvelope( + new RefreshTokenResponse(ToTokenPairDto(issuedTokens)))); + }) + .RequireRateLimiting("auth"); + + var authorized = endpoints.MapGroup("/v1").RequireAuthorization(); + + authorized.MapGet( + "/me", + async ( + ClaimsPrincipal user, + HttpContext httpContext, + MessengerApplicationService service, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var response = await service.GetMeAsync(user.RequireAccountId(), cancellationToken); + return Results.Ok(new ApiEnvelope(response)); + }); + + authorized.MapGet( + "/bootstrap", + async ( + ClaimsPrincipal user, + MessengerApplicationService service, + HttpContext httpContext, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var response = await service.GetBootstrapAsync( + user.RequireAccountId(), + user.RequireSessionId(), + BuildWsUrl(httpContext), + cancellationToken); + return Results.Ok(new ApiEnvelope(response)); + }); + + authorized.MapGet( + "/conversations", + async ( + ClaimsPrincipal user, + HttpContext httpContext, + MessengerApplicationService service, + int? limit, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var response = await service.ListConversationsAsync(user.RequireAccountId(), limit ?? 50, cancellationToken); + return Results.Ok(new ApiEnvelope>(response)); + }); + + authorized.MapGet( + "/conversations/{conversationId:guid}/messages", + async ( + Guid conversationId, + long? beforeSequence, + int? limit, + ClaimsPrincipal user, + HttpContext httpContext, + MessengerApplicationService service, + CancellationToken cancellationToken) => + { + ApplyNoStoreHeaders(httpContext.Response); + var response = await service.ListMessagesAsync( + user.RequireAccountId(), + conversationId, + beforeSequence, + limit ?? 50, + cancellationToken); + return Results.Ok(new ApiEnvelope>(response)); + }); + + authorized.MapPost( + "/conversations/{conversationId:guid}/messages", + async ( + Guid conversationId, + PostTextMessageRequest request, + ClaimsPrincipal user, + MessengerApplicationService service, + CancellationToken cancellationToken) => + { + var response = await service.PostTextMessageAsync(user.RequireAccountId(), conversationId, request, cancellationToken); + return Results.Ok(new ApiEnvelope(response)); + }); + + authorized.MapPost( + "/conversations/{conversationId:guid}/read-cursor", + async ( + Guid conversationId, + UpdateReadCursorRequest request, + ClaimsPrincipal user, + MessengerApplicationService service, + CancellationToken cancellationToken) => + { + var response = await service.UpdateReadCursorAsync(user.RequireAccountId(), conversationId, request, cancellationToken); + return Results.Ok(new ApiEnvelope(response)); + }); + + endpoints.MapGet( + "/v1/realtime/ws", + async ( + HttpContext httpContext, + IAppDbContext db, + IClock clock, + ITokenService tokenService, + WebSocketConnectionHub connectionHub, + CancellationToken cancellationToken) => + { + if (!httpContext.WebSockets.IsWebSocketRequest) + { + return Results.BadRequest(new ApiErrorEnvelope(new ApiError("websocket_required", "WebSocket 연결이 필요합니다."))); + } + + var (bearerToken, fromQueryString) = ReadBearerToken(httpContext); + var principal = fromQueryString + ? tokenService.TryReadRealtimePrincipal(bearerToken) + : tokenService.TryReadPrincipal(bearerToken); + if (principal is null) + { + return Results.Unauthorized(); + } + + var accountId = principal.RequireAccountId(); + var sessionId = principal.RequireSessionId(); + var session = await db.Sessions + .AsNoTracking() + .FirstOrDefaultAsync( + x => x.Id == sessionId && x.AccountId == accountId, + cancellationToken); + + if (session is null || !session.IsActive(clock.UtcNow)) + { + return Results.Unauthorized(); + } + + using var socket = await httpContext.WebSockets.AcceptWebSocketAsync(); + await connectionHub.AcceptConnectionAsync(accountId, sessionId, socket, cancellationToken); + return Results.Empty; + }) + .RequireRateLimiting("realtime"); + + return endpoints; + } + + private static (string Token, bool FromQueryString) ReadBearerToken(HttpContext httpContext) + { + const string prefix = "Bearer "; + var authorizationHeader = httpContext.Request.Headers.Authorization.ToString(); + if (!string.IsNullOrWhiteSpace(authorizationHeader) && + authorizationHeader.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + return (authorizationHeader[prefix.Length..].Trim(), false); + } + + var queryToken = httpContext.Request.Query["access_token"].ToString(); + if (!string.IsNullOrWhiteSpace(queryToken)) + { + return (queryToken.Trim(), true); + } + + throw new AppException( + "unauthorized", + "인증 토큰이 필요합니다.", + System.Net.HttpStatusCode.Unauthorized); + } + + private static string BuildWsUrl(HttpContext httpContext) + { + var configuration = httpContext.RequestServices.GetRequiredService(); + var configuredOrigin = configuration["ClientFacing:PublicOrigin"]?.Trim(); + + if (Uri.TryCreate(configuredOrigin, UriKind.Absolute, out var publicOrigin)) + { + var publicScheme = string.Equals(publicOrigin.Scheme, "https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws"; + return $"{publicScheme}://{publicOrigin.Authority}/v1/realtime/ws"; + } + + var scheme = string.Equals(httpContext.Request.Scheme, "https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws"; + return $"{scheme}://{httpContext.Request.Host}/v1/realtime/ws"; + } + + private static void ApplyNoStoreHeaders(HttpResponse response) + { + response.Headers["Cache-Control"] = "no-store, no-cache, max-age=0"; + response.Headers["Pragma"] = "no-cache"; + response.Headers["Expires"] = "0"; + } + + private static TokenPairDto ToTokenPairDto(IssuedTokenSet issuedTokens) + { + return new TokenPairDto( + issuedTokens.AccessToken, + issuedTokens.AccessTokenExpiresAt, + issuedTokens.RefreshToken, + issuedTokens.RefreshTokenExpiresAt); + } + + private static string HashRefreshToken(string refreshToken) + { + return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(refreshToken))); + } + + private static AppException SessionExpired() + { + return new AppException( + "session_expired", + "세션이 만료되었습니다. 다시 로그인해 주세요.", + System.Net.HttpStatusCode.Unauthorized); + } + + private static AppException SessionRevoked() + { + return new AppException( + "session_revoked", + "이 세션은 더 이상 사용할 수 없습니다. 다시 로그인해 주세요.", + System.Net.HttpStatusCode.Unauthorized); + } +} diff --git a/src/PhysOn.Api/Infrastructure/ApplicationExceptionMiddlewareExtensions.cs b/src/PhysOn.Api/Infrastructure/ApplicationExceptionMiddlewareExtensions.cs new file mode 100644 index 0000000..e26e009 --- /dev/null +++ b/src/PhysOn.Api/Infrastructure/ApplicationExceptionMiddlewareExtensions.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using PhysOn.Application.Exceptions; +using PhysOn.Contracts.Common; + +namespace PhysOn.Api.Infrastructure; + +public static class ApplicationExceptionMiddlewareExtensions +{ + public static IApplicationBuilder UsePhysOnExceptionHandling(this IApplicationBuilder app) => + app.Use(async (context, next) => + { + try + { + await next(); + } + catch (AppException exception) + { + context.Response.StatusCode = (int)exception.StatusCode; + context.Response.ContentType = "application/json"; + var payload = new ApiErrorEnvelope(new ApiError( + exception.Code, + exception.Message, + exception.Retryable, + exception.FieldErrors)); + await JsonSerializer.SerializeAsync(context.Response.Body, payload); + } + }); +} diff --git a/src/PhysOn.Api/PhysOn.Api.csproj b/src/PhysOn.Api/PhysOn.Api.csproj new file mode 100644 index 0000000..a098a2b --- /dev/null +++ b/src/PhysOn.Api/PhysOn.Api.csproj @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + net8.0 + enable + enable + + + diff --git a/src/PhysOn.Api/Program.cs b/src/PhysOn.Api/Program.cs new file mode 100644 index 0000000..5aec935 --- /dev/null +++ b/src/PhysOn.Api/Program.cs @@ -0,0 +1,79 @@ +using System.Net; +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.RateLimiting; +using PhysOn.Api.Endpoints; +using PhysOn.Api.Infrastructure; +using PhysOn.Application.Services; +using PhysOn.Infrastructure; +using PhysOn.Infrastructure.Persistence; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.Configure(options => +{ + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | + ForwardedHeaders.XForwardedHost | + ForwardedHeaders.XForwardedProto; + + if (builder.Environment.IsDevelopment() || builder.Environment.IsEnvironment("Testing")) + { + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + } + else + { + foreach (var proxy in builder.Configuration.GetSection("Network:TrustedProxies").Get() ?? []) + { + if (IPAddress.TryParse(proxy, out var address)) + { + options.KnownProxies.Add(address); + } + } + } +}); + +builder.Services.AddRateLimiter(options => +{ + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + options.AddFixedWindowLimiter("auth", limiter => + { + limiter.PermitLimit = 12; + limiter.Window = TimeSpan.FromMinutes(1); + limiter.QueueLimit = 0; + }); + options.AddFixedWindowLimiter("realtime", limiter => + { + limiter.PermitLimit = 30; + limiter.Window = TimeSpan.FromMinutes(1); + limiter.QueueLimit = 0; + }); +}); + +builder.Services.AddPhysOnInfrastructure(builder.Configuration, builder.Environment); +builder.Services.AddScoped(); + +var app = builder.Build(); + +app.UseForwardedHeaders(); +app.UsePhysOnExceptionHandling(); +app.UseRateLimiter(); +app.UseWebSockets(); +app.UseAuthentication(); +app.UseAuthorization(); + +using (var scope = app.Services.CreateScope()) +{ + var initializer = scope.ServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(); +} + +app.MapGet("/", () => Results.Ok(new { name = "PhysOn.Api", status = "ok" })); +app.MapGet("/health", () => Results.Ok(new { status = "ok" })); + +app.MapPhysOnEndpoints(); + +app.Run(); + +public partial class Program; diff --git a/src/PhysOn.Api/Properties/launchSettings.json b/src/PhysOn.Api/Properties/launchSettings.json new file mode 100644 index 0000000..4430b64 --- /dev/null +++ b/src/PhysOn.Api/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:58187", + "sslPort": 44338 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5117", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7212;http://localhost:5117", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/PhysOn.Api/appsettings.Development.json b/src/PhysOn.Api/appsettings.Development.json new file mode 100644 index 0000000..1300d75 --- /dev/null +++ b/src/PhysOn.Api/appsettings.Development.json @@ -0,0 +1,17 @@ +{ + "ConnectionStrings": { + "Main": "Data Source=vs-messenger.dev.db" + }, + "Bootstrap": { + "SeedDefaultInviteCodes": true, + "InviteCodes": [ + "ALPHA-OPEN-2026" + ] + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/PhysOn.Api/appsettings.json b/src/PhysOn.Api/appsettings.json new file mode 100644 index 0000000..6e656ff --- /dev/null +++ b/src/PhysOn.Api/appsettings.json @@ -0,0 +1,32 @@ +{ + "ConnectionStrings": { + "Main": "Data Source=vs-messenger.db" + }, + "Auth": { + "Jwt": { + "Issuer": "PhysOn", + "Audience": "PhysOn.Desktop", + "SigningKey": "vsmessenger-dev-signing-key-change-me-2026", + "AccessTokenMinutes": 15, + "RefreshTokenDays": 30, + "RealtimeTicketMinutes": 15 + } + }, + "Bootstrap": { + "SeedDefaultInviteCodes": false, + "InviteCodes": [] + }, + "ClientFacing": { + "PublicOrigin": "" + }, + "Network": { + "TrustedProxies": [] + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "localhost;127.0.0.1;[::1]" +} diff --git a/src/PhysOn.Application/Abstractions/IAppDbContext.cs b/src/PhysOn.Application/Abstractions/IAppDbContext.cs new file mode 100644 index 0000000..b0231f6 --- /dev/null +++ b/src/PhysOn.Application/Abstractions/IAppDbContext.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using PhysOn.Domain.Accounts; +using PhysOn.Domain.Conversations; +using PhysOn.Domain.Invites; +using PhysOn.Domain.Messages; + +namespace PhysOn.Application.Abstractions; + +public interface IAppDbContext +{ + DbSet Accounts { get; } + DbSet Devices { get; } + DbSet Sessions { get; } + DbSet Invites { get; } + DbSet Conversations { get; } + DbSet ConversationMembers { get; } + DbSet Messages { get; } + DatabaseFacade Database { get; } + + Task SaveChangesAsync(CancellationToken cancellationToken = default); +} diff --git a/src/PhysOn.Application/Abstractions/IClock.cs b/src/PhysOn.Application/Abstractions/IClock.cs new file mode 100644 index 0000000..ddaac49 --- /dev/null +++ b/src/PhysOn.Application/Abstractions/IClock.cs @@ -0,0 +1,6 @@ +namespace PhysOn.Application.Abstractions; + +public interface IClock +{ + DateTimeOffset UtcNow { get; } +} diff --git a/src/PhysOn.Application/Abstractions/IRealtimeNotifier.cs b/src/PhysOn.Application/Abstractions/IRealtimeNotifier.cs new file mode 100644 index 0000000..144cc00 --- /dev/null +++ b/src/PhysOn.Application/Abstractions/IRealtimeNotifier.cs @@ -0,0 +1,10 @@ +namespace PhysOn.Application.Abstractions; + +public interface IRealtimeNotifier +{ + Task PublishToAccountsAsync( + IEnumerable accountIds, + string eventName, + object payload, + CancellationToken cancellationToken = default); +} diff --git a/src/PhysOn.Application/Abstractions/ITokenService.cs b/src/PhysOn.Application/Abstractions/ITokenService.cs new file mode 100644 index 0000000..1dc23b3 --- /dev/null +++ b/src/PhysOn.Application/Abstractions/ITokenService.cs @@ -0,0 +1,23 @@ +using System.Security.Claims; +using PhysOn.Domain.Accounts; + +namespace PhysOn.Application.Abstractions; + +public interface ITokenService +{ + IssuedTokenSet IssueTokens(Account account, Session session, Device device, DateTimeOffset now); + IssuedRealtimeTicket IssueRealtimeTicket(Account account, Session session, Device device, DateTimeOffset now); + ClaimsPrincipal? TryReadPrincipal(string accessToken); + ClaimsPrincipal? TryReadRealtimePrincipal(string accessToken); +} + +public sealed record IssuedTokenSet( + string AccessToken, + DateTimeOffset AccessTokenExpiresAt, + string RefreshToken, + string RefreshTokenHash, + DateTimeOffset RefreshTokenExpiresAt); + +public sealed record IssuedRealtimeTicket( + string Token, + DateTimeOffset ExpiresAt); diff --git a/src/PhysOn.Application/Exceptions/AppException.cs b/src/PhysOn.Application/Exceptions/AppException.cs new file mode 100644 index 0000000..fd8e478 --- /dev/null +++ b/src/PhysOn.Application/Exceptions/AppException.cs @@ -0,0 +1,25 @@ +using System.Net; + +namespace PhysOn.Application.Exceptions; + +public sealed class AppException : Exception +{ + public AppException( + string code, + string message, + HttpStatusCode statusCode = HttpStatusCode.BadRequest, + bool retryable = false, + IReadOnlyDictionary? fieldErrors = null) + : base(message) + { + Code = code; + StatusCode = statusCode; + Retryable = retryable; + FieldErrors = fieldErrors; + } + + public string Code { get; } + public HttpStatusCode StatusCode { get; } + public bool Retryable { get; } + public IReadOnlyDictionary? FieldErrors { get; } +} diff --git a/src/PhysOn.Application/PhysOn.Application.csproj b/src/PhysOn.Application/PhysOn.Application.csproj new file mode 100644 index 0000000..9cfab09 --- /dev/null +++ b/src/PhysOn.Application/PhysOn.Application.csproj @@ -0,0 +1,18 @@ + + + + + + + + + + + + + net8.0 + enable + enable + + + diff --git a/src/PhysOn.Application/Services/MessengerApplicationService.cs b/src/PhysOn.Application/Services/MessengerApplicationService.cs new file mode 100644 index 0000000..c7405b4 --- /dev/null +++ b/src/PhysOn.Application/Services/MessengerApplicationService.cs @@ -0,0 +1,560 @@ +using System.Security.Cryptography; +using System.Text; +using Microsoft.EntityFrameworkCore; +using PhysOn.Application.Abstractions; +using PhysOn.Application.Exceptions; +using PhysOn.Contracts.Auth; +using PhysOn.Contracts.Common; +using PhysOn.Contracts.Conversations; +using PhysOn.Domain.Accounts; +using PhysOn.Domain.Conversations; +using PhysOn.Domain.Invites; +using PhysOn.Domain.Messages; + +namespace PhysOn.Application.Services; + +public sealed class MessengerApplicationService +{ + private const int DefaultConversationPageSize = 50; + private const int DefaultMessagePageSize = 50; + + private readonly IAppDbContext _db; + private readonly IClock _clock; + private readonly ITokenService _tokenService; + private readonly IRealtimeNotifier _realtimeNotifier; + + public MessengerApplicationService( + IAppDbContext db, + IClock clock, + ITokenService tokenService, + IRealtimeNotifier realtimeNotifier) + { + _db = db; + _clock = clock; + _tokenService = tokenService; + _realtimeNotifier = realtimeNotifier; + } + + public async Task RegisterAlphaQuickAsync( + RegisterAlphaQuickRequest request, + string wsUrl, + CancellationToken cancellationToken) + { + var now = _clock.UtcNow; + var normalizedDisplayName = NormalizeDisplayName(request.DisplayName); + var inviteCode = NormalizeInviteCode(request.InviteCode); + + var invite = await _db.Invites + .FirstOrDefaultAsync(x => x.CodeHash == HashInviteCode(inviteCode), cancellationToken) + ?? throw InvalidInviteException(); + + if (!invite.CanUse(now)) + { + throw InvalidInviteException(); + } + + var account = new Account + { + Id = Guid.NewGuid(), + DisplayName = normalizedDisplayName, + CreatedAt = now + }; + + var device = new Device + { + Id = Guid.NewGuid(), + AccountId = account.Id, + InstallId = NormalizeInstallId(request.Device.InstallId), + Platform = string.IsNullOrWhiteSpace(request.Device.Platform) ? "windows" : request.Device.Platform.Trim().ToLowerInvariant(), + DeviceName = string.IsNullOrWhiteSpace(request.Device.DeviceName) ? "Windows PC" : request.Device.DeviceName.Trim(), + AppVersion = string.IsNullOrWhiteSpace(request.Device.AppVersion) ? "0.1.0" : request.Device.AppVersion.Trim(), + CreatedAt = now, + LastSeenAt = now + }; + + var session = new Session + { + Id = Guid.NewGuid(), + AccountId = account.Id, + DeviceId = device.Id, + TokenFamilyId = Guid.NewGuid(), + CreatedAt = now, + LastSeenAt = now + }; + + var selfConversation = new Conversation + { + Id = Guid.NewGuid(), + Type = ConversationType.Self, + CreatedByAccountId = account.Id, + CreatedAt = now + }; + + var selfMembership = new ConversationMember + { + ConversationId = selfConversation.Id, + AccountId = account.Id, + Role = ConversationRole.Owner, + JoinedAt = now, + PinOrder = 0 + }; + + Conversation? inviterConversation = null; + List inviterMembers = []; + + if (invite.IssuedByAccountId is Guid inviterAccountId && inviterAccountId != account.Id) + { + inviterConversation = new Conversation + { + Id = Guid.NewGuid(), + Type = ConversationType.Direct, + CreatedByAccountId = inviterAccountId, + CreatedAt = now + }; + + inviterMembers = + [ + new ConversationMember + { + ConversationId = inviterConversation.Id, + AccountId = inviterAccountId, + Role = ConversationRole.Owner, + JoinedAt = now + }, + new ConversationMember + { + ConversationId = inviterConversation.Id, + AccountId = account.Id, + Role = ConversationRole.Member, + JoinedAt = now + } + ]; + } + + var issuedTokens = _tokenService.IssueTokens(account, session, device, now); + session.RefreshTokenHash = issuedTokens.RefreshTokenHash; + session.ExpiresAt = issuedTokens.RefreshTokenExpiresAt; + + await using var transaction = await _db.Database.BeginTransactionAsync(cancellationToken); + + _db.Accounts.Add(account); + _db.Devices.Add(device); + _db.Sessions.Add(session); + _db.Conversations.Add(selfConversation); + _db.ConversationMembers.Add(selfMembership); + + if (inviterConversation is not null) + { + _db.Conversations.Add(inviterConversation); + _db.ConversationMembers.AddRange(inviterMembers); + } + + invite.UsedCount += 1; + + await _db.SaveChangesAsync(cancellationToken); + await transaction.CommitAsync(cancellationToken); + + var bootstrap = await GetBootstrapAsync(account.Id, session.Id, wsUrl, cancellationToken); + return new RegisterAlphaQuickResponse( + bootstrap.Me, + bootstrap.Session, + new TokenPairDto( + issuedTokens.AccessToken, + issuedTokens.AccessTokenExpiresAt, + issuedTokens.RefreshToken, + issuedTokens.RefreshTokenExpiresAt), + bootstrap); + } + + public async Task GetBootstrapAsync( + Guid accountId, + Guid sessionId, + string wsUrl, + CancellationToken cancellationToken) + { + var account = await _db.Accounts + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == accountId, cancellationToken) + ?? throw NotFound("account_not_found", "사용자 정보를 찾을 수 없습니다."); + + var session = await _db.Sessions + .AsNoTracking() + .Include(x => x.Device) + .FirstOrDefaultAsync(x => x.Id == sessionId && x.AccountId == accountId, cancellationToken) + ?? throw NotFound("session_not_found", "세션 정보를 찾을 수 없습니다."); + + var conversations = await ListConversationsAsync(accountId, DefaultConversationPageSize, cancellationToken); + + return new BootstrapResponse( + ToMeDto(account), + new SessionDto(session.Id.ToString(), session.DeviceId.ToString(), session.Device?.DeviceName ?? "Windows PC", session.CreatedAt), + BuildBootstrapWsDto(account, session, session.Device, wsUrl), + conversations); + } + + public async Task GetMeAsync(Guid accountId, CancellationToken cancellationToken) + { + var account = await _db.Accounts + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == accountId, cancellationToken) + ?? throw NotFound("account_not_found", "사용자 정보를 찾을 수 없습니다."); + + return ToMeDto(account); + } + + private BootstrapWsDto BuildBootstrapWsDto(Account account, Session session, Device? device, string wsUrl) + { + var resolvedDevice = device ?? session.Device ?? throw new AppException( + "device_not_found", + "세션 기기 정보를 찾을 수 없습니다.", + System.Net.HttpStatusCode.Unauthorized); + + var issuedRealtimeTicket = _tokenService.IssueRealtimeTicket(account, session, resolvedDevice, _clock.UtcNow); + return new BootstrapWsDto(wsUrl, issuedRealtimeTicket.Token, issuedRealtimeTicket.ExpiresAt); + } + + public async Task> ListConversationsAsync( + Guid accountId, + int limit, + CancellationToken cancellationToken) + { + var pageSize = limit <= 0 ? DefaultConversationPageSize : Math.Min(limit, 100); + var memberships = await _db.ConversationMembers + .AsNoTracking() + .Where(x => x.AccountId == accountId) + .OrderBy(x => x.PinOrder.HasValue ? 0 : 1) + .ThenBy(x => x.PinOrder) + .Take(pageSize) + .ToListAsync(cancellationToken); + + if (memberships.Count == 0) + { + return new ListEnvelope([], null); + } + + var conversationIds = memberships.Select(x => x.ConversationId).ToArray(); + var conversations = await _db.Conversations + .AsNoTracking() + .Where(x => conversationIds.Contains(x.Id)) + .ToDictionaryAsync(x => x.Id, cancellationToken); + + var allMembers = await _db.ConversationMembers + .AsNoTracking() + .Where(x => conversationIds.Contains(x.ConversationId)) + .Include(x => x.Account) + .ToListAsync(cancellationToken); + + var messages = await _db.Messages + .AsNoTracking() + .Where(x => conversationIds.Contains(x.ConversationId)) + .OrderByDescending(x => x.ServerSequence) + .ToListAsync(cancellationToken); + + var lastMessages = messages + .GroupBy(x => x.ConversationId) + .ToDictionary(x => x.Key, x => x.First()); + + var summaries = memberships + .Select(membership => + { + var conversation = conversations[membership.ConversationId]; + var memberSet = allMembers.Where(x => x.ConversationId == membership.ConversationId).ToList(); + lastMessages.TryGetValue(membership.ConversationId, out var lastMessage); + return ToConversationSummaryDto(accountId, membership, conversation, memberSet, lastMessage); + }) + .OrderByDescending(x => x.IsPinned) + .ThenBy(x => x.IsPinned ? 0 : 1) + .ThenByDescending(x => x.SortKey) + .ToList(); + + return new ListEnvelope(summaries, null); + } + + public async Task> ListMessagesAsync( + Guid accountId, + Guid conversationId, + long? beforeSequence, + int limit, + CancellationToken cancellationToken) + { + await EnsureConversationMemberAsync(accountId, conversationId, cancellationToken); + + var pageSize = limit <= 0 ? DefaultMessagePageSize : Math.Min(limit, 100); + var query = _db.Messages + .AsNoTracking() + .Where(x => x.ConversationId == conversationId); + + if (beforeSequence.HasValue) + { + query = query.Where(x => x.ServerSequence < beforeSequence.Value); + } + + var items = await query + .OrderByDescending(x => x.ServerSequence) + .Take(pageSize) + .Include(x => x.SenderAccount) + .ToListAsync(cancellationToken); + + items.Reverse(); + + var nextCursor = items.Count == pageSize ? items[0].ServerSequence.ToString() : null; + var dtos = items.Select(x => ToMessageItemDto(x, accountId)).ToList(); + return new ListEnvelope(dtos, nextCursor); + } + + public async Task PostTextMessageAsync( + Guid accountId, + Guid conversationId, + PostTextMessageRequest request, + CancellationToken cancellationToken) + { + var normalizedBody = NormalizeMessageBody(request.Body); + var membership = await EnsureConversationMemberAsync(accountId, conversationId, cancellationToken); + var conversation = await _db.Conversations + .FirstOrDefaultAsync(x => x.Id == conversationId, cancellationToken) + ?? throw NotFound("conversation_not_found", "대화방을 찾을 수 없습니다."); + + var existing = await _db.Messages + .AsNoTracking() + .Include(x => x.SenderAccount) + .FirstOrDefaultAsync( + x => x.ConversationId == conversationId && x.ClientRequestId == request.ClientRequestId, + cancellationToken); + + if (existing is not null) + { + return ToMessageItemDto(existing, accountId); + } + + var now = _clock.UtcNow; + conversation.LastMessageSequence += 1; + membership.LastReadSequence = conversation.LastMessageSequence; + + var message = new Message + { + Id = Guid.NewGuid(), + ConversationId = conversationId, + SenderAccountId = accountId, + ClientRequestId = request.ClientRequestId, + ServerSequence = conversation.LastMessageSequence, + MessageType = MessageType.Text, + BodyText = normalizedBody, + CreatedAt = now + }; + + _db.Messages.Add(message); + await _db.SaveChangesAsync(cancellationToken); + + message = await _db.Messages + .AsNoTracking() + .Include(x => x.SenderAccount) + .FirstAsync(x => x.Id == message.Id, cancellationToken); + + var memberIds = await _db.ConversationMembers + .AsNoTracking() + .Where(x => x.ConversationId == conversationId) + .Select(x => x.AccountId) + .ToListAsync(cancellationToken); + + var dto = ToMessageItemDto(message, accountId); + await _realtimeNotifier.PublishToAccountsAsync(memberIds, "message.created", dto, cancellationToken); + return dto; + } + + public async Task UpdateReadCursorAsync( + Guid accountId, + Guid conversationId, + UpdateReadCursorRequest request, + CancellationToken cancellationToken) + { + var membership = await EnsureConversationMemberAsync(accountId, conversationId, cancellationToken); + var conversation = await _db.Conversations + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == conversationId, cancellationToken) + ?? throw NotFound("conversation_not_found", "대화방을 찾을 수 없습니다."); + + membership.LastReadSequence = Math.Max(membership.LastReadSequence, Math.Min(request.LastReadSequence, conversation.LastMessageSequence)); + await _db.SaveChangesAsync(cancellationToken); + + var payload = new ReadCursorUpdatedDto( + conversationId.ToString(), + accountId.ToString(), + membership.LastReadSequence, + _clock.UtcNow); + + var memberIds = await _db.ConversationMembers + .AsNoTracking() + .Where(x => x.ConversationId == conversationId) + .Select(x => x.AccountId) + .ToListAsync(cancellationToken); + + await _realtimeNotifier.PublishToAccountsAsync(memberIds, "read_cursor.updated", payload, cancellationToken); + return payload; + } + + private async Task EnsureConversationMemberAsync( + Guid accountId, + Guid conversationId, + CancellationToken cancellationToken) + { + var membership = await _db.ConversationMembers + .FirstOrDefaultAsync(x => x.AccountId == accountId && x.ConversationId == conversationId, cancellationToken); + + if (membership is null) + { + throw NotFound("conversation_not_found", "대화방을 찾을 수 없습니다."); + } + + return membership; + } + + private static ConversationSummaryDto ToConversationSummaryDto( + Guid viewerAccountId, + ConversationMember membership, + Conversation conversation, + IReadOnlyCollection members, + Message? lastMessage) + { + var otherMember = members.FirstOrDefault(x => x.AccountId != viewerAccountId)?.Account; + var title = conversation.Type switch + { + ConversationType.Self => "나에게 메시지", + ConversationType.Direct => otherMember?.DisplayName ?? "새 대화", + _ => "새 그룹" + }; + + var subtitle = conversation.Type switch + { + ConversationType.Self => "메모와 파일을 나에게 보관해 보세요.", + ConversationType.Direct => otherMember?.StatusMessage ?? "대화를 시작해 보세요.", + _ => "대화를 시작해 보세요." + }; + + MessagePreviewDto? lastMessageDto = null; + if (lastMessage is not null) + { + var senderId = lastMessage.SenderAccountId; + lastMessageDto = new MessagePreviewDto( + lastMessage.Id.ToString(), + lastMessage.BodyText, + lastMessage.CreatedAt, + senderId.ToString()); + } + + var unreadCount = Math.Max(0, (int)Math.Min(int.MaxValue, conversation.LastMessageSequence - membership.LastReadSequence)); + + return new ConversationSummaryDto( + conversation.Id.ToString(), + conversation.Type.ToString().ToLowerInvariant(), + title, + null, + subtitle, + members.Count, + membership.IsMuted, + membership.PinOrder.HasValue, + lastMessage?.CreatedAt ?? conversation.CreatedAt, + unreadCount, + membership.LastReadSequence, + lastMessageDto); + } + + private static MessageItemDto ToMessageItemDto(Message message, Guid viewerAccountId) + { + var sender = message.SenderAccount ?? throw new InvalidOperationException("SenderAccount must be loaded."); + return new MessageItemDto( + message.Id.ToString(), + message.ConversationId.ToString(), + message.ClientRequestId, + message.MessageType.ToString().ToLowerInvariant(), + message.BodyText, + message.CreatedAt, + message.EditedAt, + new MessageSenderDto(sender.Id.ToString(), sender.DisplayName, null), + sender.Id == viewerAccountId, + message.ServerSequence); + } + + private static MeDto ToMeDto(Account account) => + new(account.Id.ToString(), account.DisplayName, null, account.StatusMessage); + + private static string NormalizeDisplayName(string value) + { + var normalized = value?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalized)) + { + throw new AppException( + "display_name_required", + "이름을 입력해 주세요.", + fieldErrors: new Dictionary { ["displayName"] = "이름을 입력해 주세요." }); + } + + if (normalized.Length > 40) + { + throw new AppException( + "display_name_too_long", + "이름은 40자 이하로 입력해 주세요.", + fieldErrors: new Dictionary { ["displayName"] = "이름은 40자 이하로 입력해 주세요." }); + } + + return normalized; + } + + private static string NormalizeInviteCode(string value) + { + var normalized = value?.Trim().ToUpperInvariant() ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalized)) + { + throw InvalidInviteException(); + } + + return normalized; + } + + private static string NormalizeInstallId(string value) + { + var normalized = value?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalized)) + { + return Guid.NewGuid().ToString(); + } + + return normalized; + } + + private static string NormalizeMessageBody(string value) + { + var normalized = value?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(normalized)) + { + throw new AppException( + "message_body_required", + "메시지 내용을 입력해 주세요.", + fieldErrors: new Dictionary { ["body"] = "메시지 내용을 입력해 주세요." }); + } + + if (normalized.Length > 4000) + { + throw new AppException( + "message_body_too_long", + "메시지는 4000자 이하로 입력해 주세요.", + fieldErrors: new Dictionary { ["body"] = "메시지는 4000자 이하로 입력해 주세요." }); + } + + return normalized; + } + + private static string HashInviteCode(string inviteCode) + { + var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(inviteCode)); + return Convert.ToHexString(bytes); + } + + private static AppException InvalidInviteException() => + new( + "invite_invalid", + "초대코드가 유효하지 않습니다.", + fieldErrors: new Dictionary { ["inviteCode"] = "초대코드를 다시 확인해 주세요." }); + + private static AppException NotFound(string code, string message) => + new(code, message, System.Net.HttpStatusCode.NotFound); +} diff --git a/src/PhysOn.Contracts/Auth/AuthContracts.cs b/src/PhysOn.Contracts/Auth/AuthContracts.cs new file mode 100644 index 0000000..74cea56 --- /dev/null +++ b/src/PhysOn.Contracts/Auth/AuthContracts.cs @@ -0,0 +1,42 @@ +using PhysOn.Contracts.Common; +using PhysOn.Contracts.Conversations; + +namespace PhysOn.Contracts.Auth; + +public sealed record DeviceRegistrationDto( + string InstallId, + string Platform, + string DeviceName, + string AppVersion); + +public sealed record RegisterAlphaQuickRequest( + string DisplayName, + string InviteCode, + DeviceRegistrationDto Device); + +public sealed record RefreshTokenRequest(string RefreshToken); + +public sealed record TokenPairDto( + string AccessToken, + DateTimeOffset AccessTokenExpiresAt, + string RefreshToken, + DateTimeOffset RefreshTokenExpiresAt); + +public sealed record BootstrapWsDto( + string Url, + string Ticket, + DateTimeOffset TicketExpiresAt); + +public sealed record BootstrapResponse( + MeDto Me, + SessionDto Session, + BootstrapWsDto Ws, + ListEnvelope Conversations); + +public sealed record RegisterAlphaQuickResponse( + MeDto Account, + SessionDto Session, + TokenPairDto Tokens, + BootstrapResponse Bootstrap); + +public sealed record RefreshTokenResponse(TokenPairDto Tokens); diff --git a/src/PhysOn.Contracts/Common/ApiEnvelope.cs b/src/PhysOn.Contracts/Common/ApiEnvelope.cs new file mode 100644 index 0000000..aa70620 --- /dev/null +++ b/src/PhysOn.Contracts/Common/ApiEnvelope.cs @@ -0,0 +1,13 @@ +namespace PhysOn.Contracts.Common; + +public sealed record ApiEnvelope(T Data); + +public sealed record ListEnvelope(IReadOnlyList Items, string? NextCursor); + +public sealed record ApiErrorEnvelope(ApiError Error); + +public sealed record ApiError( + string Code, + string Message, + bool Retryable = false, + IReadOnlyDictionary? FieldErrors = null); diff --git a/src/PhysOn.Contracts/Common/IdentityDtos.cs b/src/PhysOn.Contracts/Common/IdentityDtos.cs new file mode 100644 index 0000000..47d9993 --- /dev/null +++ b/src/PhysOn.Contracts/Common/IdentityDtos.cs @@ -0,0 +1,13 @@ +namespace PhysOn.Contracts.Common; + +public sealed record MeDto( + string UserId, + string DisplayName, + string? ProfileImageUrl, + string? StatusMessage); + +public sealed record SessionDto( + string SessionId, + string DeviceId, + string DeviceName, + DateTimeOffset CreatedAt); diff --git a/src/PhysOn.Contracts/Conversations/ConversationContracts.cs b/src/PhysOn.Contracts/Conversations/ConversationContracts.cs new file mode 100644 index 0000000..39ef83a --- /dev/null +++ b/src/PhysOn.Contracts/Conversations/ConversationContracts.cs @@ -0,0 +1,50 @@ +namespace PhysOn.Contracts.Conversations; + +public sealed record MessagePreviewDto( + string MessageId, + string Text, + DateTimeOffset CreatedAt, + string SenderUserId); + +public sealed record ConversationSummaryDto( + string ConversationId, + string Type, + string Title, + string? AvatarUrl, + string Subtitle, + int MemberCount, + bool IsMuted, + bool IsPinned, + DateTimeOffset SortKey, + int UnreadCount, + long LastReadSequence, + MessagePreviewDto? LastMessage); + +public sealed record MessageSenderDto( + string UserId, + string DisplayName, + string? ProfileImageUrl); + +public sealed record MessageItemDto( + string MessageId, + string ConversationId, + Guid ClientMessageId, + string Kind, + string Text, + DateTimeOffset CreatedAt, + DateTimeOffset? EditedAt, + MessageSenderDto Sender, + bool IsMine, + long ServerSequence); + +public sealed record PostTextMessageRequest( + Guid ClientRequestId, + string Body); + +public sealed record UpdateReadCursorRequest(long LastReadSequence); + +public sealed record ReadCursorUpdatedDto( + string ConversationId, + string AccountId, + long LastReadSequence, + DateTimeOffset UpdatedAt); diff --git a/src/PhysOn.Contracts/PhysOn.Contracts.csproj b/src/PhysOn.Contracts/PhysOn.Contracts.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/src/PhysOn.Contracts/PhysOn.Contracts.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/src/PhysOn.Contracts/Realtime/RealtimeContracts.cs b/src/PhysOn.Contracts/Realtime/RealtimeContracts.cs new file mode 100644 index 0000000..7451595 --- /dev/null +++ b/src/PhysOn.Contracts/Realtime/RealtimeContracts.cs @@ -0,0 +1,9 @@ +namespace PhysOn.Contracts.Realtime; + +public sealed record RealtimeEventEnvelope( + string Event, + string EventId, + DateTimeOffset OccurredAt, + object Data); + +public sealed record SessionConnectedDto(string SessionId); diff --git a/src/PhysOn.Desktop/App.axaml b/src/PhysOn.Desktop/App.axaml new file mode 100644 index 0000000..93cd91b --- /dev/null +++ b/src/PhysOn.Desktop/App.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/PhysOn.Desktop/App.axaml.cs b/src/PhysOn.Desktop/App.axaml.cs new file mode 100644 index 0000000..8e0dfa4 --- /dev/null +++ b/src/PhysOn.Desktop/App.axaml.cs @@ -0,0 +1,34 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using PhysOn.Desktop.Services; +using PhysOn.Desktop.ViewModels; +using PhysOn.Desktop.Views; + +namespace PhysOn.Desktop; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var conversationWindowManager = new ConversationWindowManager(); + var workspaceLayoutStore = new WorkspaceLayoutStore(); + var viewModel = new MainWindowViewModel(conversationWindowManager, workspaceLayoutStore); + desktop.MainWindow = new MainWindow + { + DataContext = viewModel, + }; + + _ = viewModel.InitializeAsync(); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/src/PhysOn.Desktop/Assets/avalonia-logo.ico b/src/PhysOn.Desktop/Assets/avalonia-logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..f7da8bb5863b7cecec2adcdebd948fe2f9418d0c GIT binary patch literal 175875 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 + + WinExe + net8.0 + enable + enable + app.manifest + true + + + + + + + + + + + + + None + All + + + + + + + + + diff --git a/src/PhysOn.Desktop/Program.cs b/src/PhysOn.Desktop/Program.cs new file mode 100644 index 0000000..7963802 --- /dev/null +++ b/src/PhysOn.Desktop/Program.cs @@ -0,0 +1,23 @@ +using Avalonia; +using System; + +namespace PhysOn.Desktop; + +sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() +#if DEBUG + .WithDeveloperTools() +#endif + .LogToTrace(); +} diff --git a/src/PhysOn.Desktop/Services/ConversationWindowManager.cs b/src/PhysOn.Desktop/Services/ConversationWindowManager.cs new file mode 100644 index 0000000..0705a91 --- /dev/null +++ b/src/PhysOn.Desktop/Services/ConversationWindowManager.cs @@ -0,0 +1,47 @@ +using Avalonia.Controls; +using PhysOn.Desktop.ViewModels; +using PhysOn.Desktop.Views; + +namespace PhysOn.Desktop.Services; + +public sealed class ConversationWindowManager : IConversationWindowManager +{ + private readonly Dictionary _openWindows = new(StringComparer.Ordinal); + + public event Action? WindowCountChanged; + + public Task ShowOrFocusAsync(ConversationWindowLaunch launchContext, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_openWindows.TryGetValue(launchContext.ConversationId, out var existingWindow)) + { + existingWindow.WindowState = WindowState.Normal; + existingWindow.Activate(); + return Task.CompletedTask; + } + + var viewModel = new ConversationWindowViewModel(launchContext); + var window = new ConversationWindow + { + DataContext = viewModel, + Title = launchContext.ConversationTitle + }; + + window.Closed += (_, _) => _ = HandleWindowClosedAsync(launchContext.ConversationId, viewModel); + + _openWindows[launchContext.ConversationId] = window; + WindowCountChanged?.Invoke(_openWindows.Count); + + window.Show(); + _ = viewModel.InitializeAsync(); + return Task.CompletedTask; + } + + private async Task HandleWindowClosedAsync(string conversationId, ConversationWindowViewModel viewModel) + { + _openWindows.Remove(conversationId); + WindowCountChanged?.Invoke(_openWindows.Count); + await viewModel.DisposeAsync(); + } +} diff --git a/src/PhysOn.Desktop/Services/IConversationWindowManager.cs b/src/PhysOn.Desktop/Services/IConversationWindowManager.cs new file mode 100644 index 0000000..f4f68b6 --- /dev/null +++ b/src/PhysOn.Desktop/Services/IConversationWindowManager.cs @@ -0,0 +1,16 @@ +namespace PhysOn.Desktop.Services; + +public interface IConversationWindowManager +{ + event Action? WindowCountChanged; + + Task ShowOrFocusAsync(ConversationWindowLaunch launchContext, CancellationToken cancellationToken = default); +} + +public sealed record ConversationWindowLaunch( + string ApiBaseUrl, + string AccessToken, + string DisplayName, + string ConversationId, + string ConversationTitle, + string ConversationSubtitle); diff --git a/src/PhysOn.Desktop/Services/PhysOnApiClient.cs b/src/PhysOn.Desktop/Services/PhysOnApiClient.cs new file mode 100644 index 0000000..d9d5294 --- /dev/null +++ b/src/PhysOn.Desktop/Services/PhysOnApiClient.cs @@ -0,0 +1,127 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using PhysOn.Contracts.Auth; +using PhysOn.Contracts.Common; +using PhysOn.Contracts.Conversations; + +namespace PhysOn.Desktop.Services; + +public sealed class PhysOnApiClient +{ + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) + { + PropertyNameCaseInsensitive = true + }; + + public Task RegisterAlphaQuickAsync( + string apiBaseUrl, + RegisterAlphaQuickRequest request, + CancellationToken cancellationToken) => + SendAsync( + apiBaseUrl, + HttpMethod.Post, + "/v1/auth/register/alpha-quick", + null, + request, + cancellationToken); + + public Task GetBootstrapAsync( + string apiBaseUrl, + string accessToken, + CancellationToken cancellationToken) => + SendAsync( + apiBaseUrl, + HttpMethod.Get, + "/v1/bootstrap", + accessToken, + null, + cancellationToken); + + public Task> GetMessagesAsync( + string apiBaseUrl, + string accessToken, + string conversationId, + CancellationToken cancellationToken) => + SendAsync>( + apiBaseUrl, + HttpMethod.Get, + $"/v1/conversations/{conversationId}/messages", + accessToken, + null, + cancellationToken); + + public Task SendTextMessageAsync( + string apiBaseUrl, + string accessToken, + string conversationId, + PostTextMessageRequest request, + CancellationToken cancellationToken) => + SendAsync( + apiBaseUrl, + HttpMethod.Post, + $"/v1/conversations/{conversationId}/messages", + accessToken, + request, + cancellationToken); + + public Task UpdateReadCursorAsync( + string apiBaseUrl, + string accessToken, + string conversationId, + UpdateReadCursorRequest request, + CancellationToken cancellationToken) => + SendAsync( + apiBaseUrl, + HttpMethod.Post, + $"/v1/conversations/{conversationId}/read-cursor", + accessToken, + request, + cancellationToken); + + private static async Task SendAsync( + string apiBaseUrl, + HttpMethod method, + string path, + string? accessToken, + object? body, + CancellationToken cancellationToken) + { + using var client = new HttpClient + { + BaseAddress = new Uri(EnsureTrailingSlash(apiBaseUrl)), + Timeout = TimeSpan.FromSeconds(20) + }; + + using var request = new HttpRequestMessage(method, path.TrimStart('/')); + if (!string.IsNullOrWhiteSpace(accessToken)) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + } + + if (body is not null) + { + request.Content = JsonContent.Create(body, options: JsonOptions); + } + + using var response = await client.SendAsync(request, cancellationToken); + var payload = await response.Content.ReadAsStringAsync(cancellationToken); + + if (!response.IsSuccessStatusCode) + { + var error = JsonSerializer.Deserialize(payload, JsonOptions); + throw new InvalidOperationException(error?.Error.Message ?? $"요청이 실패했습니다. ({response.StatusCode})"); + } + + var envelope = JsonSerializer.Deserialize>(payload, JsonOptions); + if (envelope is null) + { + throw new InvalidOperationException("서버 응답을 읽지 못했습니다."); + } + + return envelope.Data; + } + + private static string EnsureTrailingSlash(string apiBaseUrl) => + apiBaseUrl.EndsWith("/", StringComparison.Ordinal) ? apiBaseUrl : $"{apiBaseUrl}/"; +} diff --git a/src/PhysOn.Desktop/Services/PhysOnRealtimeClient.cs b/src/PhysOn.Desktop/Services/PhysOnRealtimeClient.cs new file mode 100644 index 0000000..5e6b40d --- /dev/null +++ b/src/PhysOn.Desktop/Services/PhysOnRealtimeClient.cs @@ -0,0 +1,287 @@ +using System.Net.WebSockets; +using System.Text; +using System.Text.Json; +using PhysOn.Contracts.Conversations; +using PhysOn.Contracts.Realtime; + +namespace PhysOn.Desktop.Services; + +public enum RealtimeConnectionState +{ + Idle, + Connecting, + Connected, + Reconnecting, + Disconnected +} + +public sealed class PhysOnRealtimeClient : IAsyncDisposable +{ + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) + { + PropertyNameCaseInsensitive = true + }; + + private readonly SemaphoreSlim _lifecycleLock = new(1, 1); + + private ClientWebSocket? _socket; + private CancellationTokenSource? _connectionCts; + private Task? _connectionTask; + private bool _disposed; + + public event Action? ConnectionStateChanged; + public event Action? SessionConnected; + public event Action? MessageCreated; + public event Action? ReadCursorUpdated; + + public async Task ConnectAsync(string wsUrl, string accessToken, CancellationToken cancellationToken = default) + { + await _lifecycleLock.WaitAsync(cancellationToken); + try + { + await DisconnectCoreAsync(); + + if (string.IsNullOrWhiteSpace(wsUrl) || string.IsNullOrWhiteSpace(accessToken)) + { + NotifyStateChanged(RealtimeConnectionState.Idle); + return; + } + + _connectionCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _connectionTask = RunConnectionLoopAsync(new Uri(wsUrl), accessToken, _connectionCts.Token); + } + finally + { + _lifecycleLock.Release(); + } + } + + public async Task DisconnectAsync(CancellationToken cancellationToken = default) + { + await _lifecycleLock.WaitAsync(cancellationToken); + try + { + await DisconnectCoreAsync(); + } + finally + { + _lifecycleLock.Release(); + } + } + + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + _disposed = true; + await DisconnectAsync(); + _lifecycleLock.Dispose(); + } + + private async Task DisconnectCoreAsync() + { + var cts = _connectionCts; + var socket = _socket; + var task = _connectionTask; + + _connectionCts = null; + _connectionTask = null; + _socket = null; + + if (cts is null && socket is null && task is null) + { + NotifyStateChanged(RealtimeConnectionState.Idle); + return; + } + + try + { + cts?.Cancel(); + } + catch + { + // Ignore cancellation races during shutdown. + } + + if (socket is not null) + { + try + { + if (socket.State is WebSocketState.Open or WebSocketState.CloseReceived) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "shutdown", CancellationToken.None); + } + } + catch + { + // Ignore close failures during shutdown. + } + finally + { + socket.Dispose(); + } + } + + if (task is not null) + { + try + { + await task; + } + catch (OperationCanceledException) + { + // Expected when the client is disposed or explicitly disconnected. + } + } + + cts?.Dispose(); + NotifyStateChanged(RealtimeConnectionState.Idle); + } + + private async Task RunConnectionLoopAsync(Uri wsUri, string accessToken, CancellationToken cancellationToken) + { + var reconnecting = false; + + while (!cancellationToken.IsCancellationRequested) + { + using var socket = new ClientWebSocket(); + socket.Options.SetRequestHeader("Authorization", $"Bearer {accessToken}"); + _socket = socket; + + NotifyStateChanged(reconnecting ? RealtimeConnectionState.Reconnecting : RealtimeConnectionState.Connecting); + + try + { + await socket.ConnectAsync(wsUri, cancellationToken); + NotifyStateChanged(RealtimeConnectionState.Connected); + await ReceiveLoopAsync(socket, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + break; + } + catch + { + NotifyStateChanged(RealtimeConnectionState.Disconnected); + } + finally + { + if (ReferenceEquals(_socket, socket)) + { + _socket = null; + } + + try + { + if (socket.State is WebSocketState.Open or WebSocketState.CloseReceived) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None); + } + } + catch + { + // Ignore connection teardown errors. + } + } + + if (cancellationToken.IsCancellationRequested) + { + break; + } + + reconnecting = true; + NotifyStateChanged(RealtimeConnectionState.Disconnected); + + try + { + await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); + } + catch (OperationCanceledException) + { + break; + } + } + + NotifyStateChanged(RealtimeConnectionState.Idle); + } + + private async Task ReceiveLoopAsync(ClientWebSocket socket, CancellationToken cancellationToken) + { + var buffer = new byte[4096]; + + while (!cancellationToken.IsCancellationRequested && socket.State == WebSocketState.Open) + { + using var stream = new MemoryStream(); + WebSocketReceiveResult result; + + do + { + result = await socket.ReceiveAsync(buffer, cancellationToken); + if (result.MessageType == WebSocketMessageType.Close) + { + return; + } + + if (result.Count > 0) + { + stream.Write(buffer, 0, result.Count); + } + } while (!result.EndOfMessage); + + if (result.MessageType != WebSocketMessageType.Text || stream.Length == 0) + { + continue; + } + + stream.Position = 0; + DispatchIncomingEvent(stream); + } + } + + private void DispatchIncomingEvent(Stream payloadStream) + { + using var document = JsonDocument.Parse(payloadStream); + if (!document.RootElement.TryGetProperty("event", out var eventProperty)) + { + return; + } + + if (!document.RootElement.TryGetProperty("data", out var dataProperty)) + { + return; + } + + var eventName = eventProperty.GetString(); + switch (eventName) + { + case "session.connected": + var sessionConnected = dataProperty.Deserialize(JsonOptions); + if (sessionConnected is not null) + { + SessionConnected?.Invoke(sessionConnected); + } + break; + + case "message.created": + var messageCreated = dataProperty.Deserialize(JsonOptions); + if (messageCreated is not null) + { + MessageCreated?.Invoke(messageCreated); + } + break; + + case "read_cursor.updated": + var readCursorUpdated = dataProperty.Deserialize(JsonOptions); + if (readCursorUpdated is not null) + { + ReadCursorUpdated?.Invoke(readCursorUpdated); + } + break; + } + } + + private void NotifyStateChanged(RealtimeConnectionState state) => ConnectionStateChanged?.Invoke(state); +} diff --git a/src/PhysOn.Desktop/Services/SessionStore.cs b/src/PhysOn.Desktop/Services/SessionStore.cs new file mode 100644 index 0000000..3ad725f --- /dev/null +++ b/src/PhysOn.Desktop/Services/SessionStore.cs @@ -0,0 +1,136 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using PhysOn.Desktop.Models; + +namespace PhysOn.Desktop.Services; + +public sealed class SessionStore +{ + private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("PhysOn.Desktop.Session"); + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) + { + WriteIndented = true + }; + + private static readonly byte[] WindowsHeader = "VSMW1"u8.ToArray(); + + private readonly string _sessionDirectory = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "PhysOn"); + private readonly string _sessionPath; + private readonly string _legacySessionPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "PhysOn", + "session.json"); + + public SessionStore() + { + _sessionPath = OperatingSystem.IsWindows() + ? Path.Combine(_sessionDirectory, "session.dat") + : Path.Combine(_sessionDirectory, "session.json"); + } + + public async Task LoadAsync() + { + if (File.Exists(_sessionPath)) + { + return await LoadFromPathAsync(_sessionPath); + } + + if (File.Exists(_legacySessionPath)) + { + return await LoadFromPathAsync(_legacySessionPath); + } + + return null; + } + + public async Task SaveAsync(DesktopSession session) + { + Directory.CreateDirectory(_sessionDirectory); + ApplyDirectoryPermissions(_sessionDirectory); + + var payload = JsonSerializer.SerializeToUtf8Bytes(session, JsonOptions); + if (OperatingSystem.IsWindows()) + { + payload = WindowsHeader + .Concat(ProtectedData.Protect(payload, Entropy, DataProtectionScope.CurrentUser)) + .ToArray(); + } + + await File.WriteAllBytesAsync(_sessionPath, payload); + ApplyFilePermissions(_sessionPath); + + if (File.Exists(_legacySessionPath)) + { + File.Delete(_legacySessionPath); + } + } + + public Task ClearAsync() + { + if (File.Exists(_sessionPath)) + { + File.Delete(_sessionPath); + } + + if (File.Exists(_legacySessionPath)) + { + File.Delete(_legacySessionPath); + } + + return Task.CompletedTask; + } + + private async Task LoadFromPathAsync(string path) + { + try + { + var payload = await File.ReadAllBytesAsync(path); + if (payload.Length == 0) + { + return null; + } + + if (OperatingSystem.IsWindows() && payload.AsSpan().StartsWith(WindowsHeader)) + { + var encrypted = payload.AsSpan(WindowsHeader.Length).ToArray(); + var decrypted = ProtectedData.Unprotect(encrypted, Entropy, DataProtectionScope.CurrentUser); + return JsonSerializer.Deserialize(decrypted, JsonOptions); + } + + return JsonSerializer.Deserialize(payload, JsonOptions); + } + catch (CryptographicException) + { + return null; + } + catch (JsonException) + { + return null; + } + } + + private static void ApplyDirectoryPermissions(string path) + { + if (OperatingSystem.IsWindows()) + { + File.SetAttributes(path, FileAttributes.Directory | FileAttributes.Hidden); + return; + } + + File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + } + + private static void ApplyFilePermissions(string path) + { + if (OperatingSystem.IsWindows()) + { + File.SetAttributes(path, FileAttributes.Hidden); + return; + } + + File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite); + } +} diff --git a/src/PhysOn.Desktop/Services/WorkspaceLayoutStore.cs b/src/PhysOn.Desktop/Services/WorkspaceLayoutStore.cs new file mode 100644 index 0000000..01c6b66 --- /dev/null +++ b/src/PhysOn.Desktop/Services/WorkspaceLayoutStore.cs @@ -0,0 +1,51 @@ +using System.Text.Json; +using PhysOn.Desktop.Models; + +namespace PhysOn.Desktop.Services; + +public sealed class WorkspaceLayoutStore +{ + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web) + { + WriteIndented = true + }; + + private readonly string _directoryPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "PhysOn"); + private readonly string _layoutPath; + + public WorkspaceLayoutStore() + { + _layoutPath = Path.Combine(_directoryPath, "workspace-layout.json"); + } + + public async Task LoadAsync() + { + if (!File.Exists(_layoutPath)) + { + return null; + } + + try + { + var payload = await File.ReadAllTextAsync(_layoutPath); + return JsonSerializer.Deserialize(payload, JsonOptions); + } + catch (IOException) + { + return null; + } + catch (JsonException) + { + return null; + } + } + + public async Task SaveAsync(DesktopWorkspaceLayout layout) + { + Directory.CreateDirectory(_directoryPath); + var payload = JsonSerializer.Serialize(layout, JsonOptions); + await File.WriteAllTextAsync(_layoutPath, payload); + } +} diff --git a/src/PhysOn.Desktop/ViewLocator.cs b/src/PhysOn.Desktop/ViewLocator.cs new file mode 100644 index 0000000..600900f --- /dev/null +++ b/src/PhysOn.Desktop/ViewLocator.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using PhysOn.Desktop.ViewModels; + +namespace PhysOn.Desktop; + +/// +/// Given a view model, returns the corresponding view if possible. +/// +[RequiresUnreferencedCode( + "Default implementation of ViewLocator involves reflection which may be trimmed away.", + Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? param) + { + if (param is null) + return null; + + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/src/PhysOn.Desktop/ViewModels/ConversationRowViewModel.cs b/src/PhysOn.Desktop/ViewModels/ConversationRowViewModel.cs new file mode 100644 index 0000000..dcb13d6 --- /dev/null +++ b/src/PhysOn.Desktop/ViewModels/ConversationRowViewModel.cs @@ -0,0 +1,32 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace PhysOn.Desktop.ViewModels; + +public partial class ConversationRowViewModel : ViewModelBase +{ + [ObservableProperty] private string conversationId = string.Empty; + [ObservableProperty] private string title = string.Empty; + [ObservableProperty] private string subtitle = string.Empty; + [ObservableProperty] private string lastMessageText = string.Empty; + [ObservableProperty] private string metaText = string.Empty; + [ObservableProperty] private int unreadCount; + [ObservableProperty] private bool isPinned; + [ObservableProperty] private bool isSelected; + [ObservableProperty] private long lastReadSequence; + [ObservableProperty] private DateTimeOffset sortKey; + + public bool HasUnread => UnreadCount > 0; + public string UnreadBadgeText => UnreadCount.ToString(); + public string AvatarText => string.IsNullOrWhiteSpace(Title) ? "VS" : Title.Trim()[..Math.Min(2, Title.Trim().Length)]; + + partial void OnUnreadCountChanged(int value) + { + OnPropertyChanged(nameof(HasUnread)); + OnPropertyChanged(nameof(UnreadBadgeText)); + } + + partial void OnTitleChanged(string value) + { + OnPropertyChanged(nameof(AvatarText)); + } +} diff --git a/src/PhysOn.Desktop/ViewModels/ConversationWindowViewModel.cs b/src/PhysOn.Desktop/ViewModels/ConversationWindowViewModel.cs new file mode 100644 index 0000000..a714daa --- /dev/null +++ b/src/PhysOn.Desktop/ViewModels/ConversationWindowViewModel.cs @@ -0,0 +1,224 @@ +using System.Collections.ObjectModel; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PhysOn.Contracts.Conversations; +using PhysOn.Contracts.Realtime; +using PhysOn.Desktop.Services; + +namespace PhysOn.Desktop.ViewModels; + +public partial class ConversationWindowViewModel : ViewModelBase, IAsyncDisposable +{ + private readonly PhysOnApiClient _apiClient = new(); + private readonly PhysOnRealtimeClient _realtimeClient = new(); + private readonly ConversationWindowLaunch _launchContext; + + public ConversationWindowViewModel(ConversationWindowLaunch launchContext) + { + _launchContext = launchContext; + ConversationTitle = launchContext.ConversationTitle; + ConversationSubtitle = launchContext.ConversationSubtitle; + SendMessageCommand = new AsyncRelayCommand(SendMessageAsync, CanSendMessage); + ReloadCommand = new AsyncRelayCommand(LoadMessagesAsync, () => !IsBusy); + + _realtimeClient.ConnectionStateChanged += HandleRealtimeConnectionStateChanged; + _realtimeClient.MessageCreated += HandleMessageCreated; + } + + public ObservableCollection Messages { get; } = []; + + public IAsyncRelayCommand SendMessageCommand { get; } + public IAsyncRelayCommand ReloadCommand { get; } + + [ObservableProperty] private string conversationTitle = string.Empty; + [ObservableProperty] private string conversationSubtitle = string.Empty; + [ObservableProperty] private string composerText = string.Empty; + [ObservableProperty] private string statusText = "·"; + [ObservableProperty] private bool isBusy; + [ObservableProperty] private string? errorText; + + public string ConversationGlyph => + string.IsNullOrWhiteSpace(ConversationTitle) ? "PO" : ConversationTitle.Trim()[..Math.Min(2, ConversationTitle.Trim().Length)]; + + public bool HasErrorText => !string.IsNullOrWhiteSpace(ErrorText); + + public async Task InitializeAsync() + { + await LoadMessagesAsync(); + + try + { + var bootstrap = await _apiClient.GetBootstrapAsync( + _launchContext.ApiBaseUrl, + _launchContext.AccessToken, + CancellationToken.None); + await _realtimeClient.ConnectAsync(bootstrap.Ws.Url, _launchContext.AccessToken, CancellationToken.None); + } + catch (Exception exception) + { + ErrorText = exception.Message; + } + } + + public async Task SendMessageFromShortcutAsync() + { + if (SendMessageCommand.CanExecute(null)) + { + await SendMessageCommand.ExecuteAsync(null); + } + } + + public async ValueTask DisposeAsync() + { + await _realtimeClient.DisposeAsync(); + } + + partial void OnComposerTextChanged(string value) => SendMessageCommand.NotifyCanExecuteChanged(); + partial void OnErrorTextChanged(string? value) => OnPropertyChanged(nameof(HasErrorText)); + partial void OnConversationTitleChanged(string value) => OnPropertyChanged(nameof(ConversationGlyph)); + + private async Task LoadMessagesAsync() + { + if (IsBusy) + { + return; + } + + try + { + IsBusy = true; + ErrorText = null; + StatusText = "◌"; + + var items = await _apiClient.GetMessagesAsync( + _launchContext.ApiBaseUrl, + _launchContext.AccessToken, + _launchContext.ConversationId, + CancellationToken.None); + + Messages.Clear(); + foreach (var item in items.Items.OrderBy(message => message.ServerSequence)) + { + Messages.Add(MapMessage(item)); + } + + StatusText = "●"; + } + catch (Exception exception) + { + ErrorText = exception.Message; + StatusText = "×"; + } + finally + { + IsBusy = false; + ReloadCommand.NotifyCanExecuteChanged(); + SendMessageCommand.NotifyCanExecuteChanged(); + } + } + + private async Task SendMessageAsync() + { + if (!CanSendMessage()) + { + return; + } + + var draft = ComposerText.Trim(); + var clientMessageId = Guid.NewGuid(); + ComposerText = string.Empty; + + var pendingMessage = new MessageRowViewModel + { + MessageId = $"pending-{Guid.NewGuid():N}", + ClientMessageId = clientMessageId, + Text = draft, + SenderName = _launchContext.DisplayName, + MetaText = "보내는 중", + IsMine = true, + IsPending = true, + ServerSequence = Messages.Count == 0 ? 1 : Messages[^1].ServerSequence + 1 + }; + + Messages.Add(pendingMessage); + + try + { + var committed = await _apiClient.SendTextMessageAsync( + _launchContext.ApiBaseUrl, + _launchContext.AccessToken, + _launchContext.ConversationId, + new PostTextMessageRequest(clientMessageId, draft), + CancellationToken.None); + + Messages.Remove(pendingMessage); + UpsertMessage(MapMessage(committed)); + StatusText = "●"; + } + catch (Exception exception) + { + pendingMessage.IsPending = false; + pendingMessage.IsFailed = true; + pendingMessage.MetaText = "전송 실패"; + ErrorText = exception.Message; + } + } + + private bool CanSendMessage() => !IsBusy && !string.IsNullOrWhiteSpace(ComposerText); + + private void HandleRealtimeConnectionStateChanged(RealtimeConnectionState state) + { + Dispatcher.UIThread.Post(() => + { + StatusText = state switch + { + RealtimeConnectionState.Connected => "●", + RealtimeConnectionState.Reconnecting => "◔", + RealtimeConnectionState.Disconnected => "○", + RealtimeConnectionState.Connecting => "◌", + _ => StatusText + }; + }); + } + + private void HandleMessageCreated(MessageItemDto payload) + { + if (!string.Equals(payload.ConversationId, _launchContext.ConversationId, StringComparison.Ordinal)) + { + return; + } + + Dispatcher.UIThread.Post(() => UpsertMessage(MapMessage(payload))); + } + + private static MessageRowViewModel MapMessage(MessageItemDto item) + { + return new MessageRowViewModel + { + MessageId = item.MessageId, + ClientMessageId = item.ClientMessageId, + Text = item.Text, + SenderName = item.Sender.DisplayName, + MetaText = item.CreatedAt.LocalDateTime.ToString("HH:mm"), + IsMine = item.IsMine, + ServerSequence = item.ServerSequence + }; + } + + private void UpsertMessage(MessageRowViewModel next) + { + var existing = Messages.FirstOrDefault(item => + string.Equals(item.MessageId, next.MessageId, StringComparison.Ordinal) || + (next.ClientMessageId != Guid.Empty && item.ClientMessageId == next.ClientMessageId)); + + if (existing is not null) + { + var index = Messages.IndexOf(existing); + Messages[index] = next; + return; + } + + Messages.Add(next); + } +} diff --git a/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs b/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..bc14f24 --- /dev/null +++ b/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,1052 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Avalonia; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using PhysOn.Contracts.Auth; +using PhysOn.Contracts.Conversations; +using PhysOn.Contracts.Realtime; +using PhysOn.Desktop.Models; +using PhysOn.Desktop.Services; + +namespace PhysOn.Desktop.ViewModels; + +public partial class MainWindowViewModel : ViewModelBase, IAsyncDisposable +{ + private const string DefaultApiBaseUrl = "https://vstalk.phy.kr"; + private readonly PhysOnApiClient _apiClient = new(); + private readonly SessionStore _sessionStore = new(); + private readonly PhysOnRealtimeClient _realtimeClient = new(); + private readonly WorkspaceLayoutStore _workspaceLayoutStore; + private readonly IConversationWindowManager _conversationWindowManager; + + private DesktopSession? _session; + private string? _currentUserId; + + public MainWindowViewModel() + : this(new ConversationWindowManager(), new WorkspaceLayoutStore()) + { + } + + public MainWindowViewModel( + IConversationWindowManager conversationWindowManager, + WorkspaceLayoutStore workspaceLayoutStore) + { + _conversationWindowManager = conversationWindowManager; + _workspaceLayoutStore = workspaceLayoutStore; + Messages.CollectionChanged += HandleMessagesCollectionChanged; + + SignInCommand = new AsyncRelayCommand(SignInAsync, CanSignIn); + SendMessageCommand = new AsyncRelayCommand(SendMessageAsync, CanSendMessage); + SignOutCommand = new AsyncRelayCommand(SignOutAsync); + ReloadCommand = new AsyncRelayCommand(ReloadAsync, () => IsAuthenticated && !IsBusy); + ToggleAdvancedSettingsCommand = new RelayCommand(() => ShowAdvancedSettings = !ShowAdvancedSettings); + ShowAllConversationsCommand = new RelayCommand(() => SelectedListFilter = "all"); + ShowUnreadConversationsCommand = new RelayCommand(() => SelectedListFilter = "unread"); + ShowPinnedConversationsCommand = new RelayCommand(() => SelectedListFilter = "pinned"); + ToggleCompactModeCommand = new RelayCommand(() => IsCompactDensity = !IsCompactDensity); + ToggleInspectorCommand = new RelayCommand(() => IsInspectorVisible = !IsInspectorVisible); + ToggleConversationPaneCommand = new RelayCommand(() => IsConversationPaneCollapsed = !IsConversationPaneCollapsed); + ResetWorkspaceCommand = new RelayCommand(ResetWorkspaceLayout); + DetachConversationCommand = new AsyncRelayCommand(DetachConversationAsync, CanDetachConversation); + DetachConversationRowCommand = new AsyncRelayCommand(DetachConversationRowAsync, CanDetachConversationRow); + SelectConversationCommand = new RelayCommand(conversation => + { + if (conversation is not null) + { + SelectedConversation = conversation; + } + }); + + _realtimeClient.ConnectionStateChanged += HandleRealtimeConnectionStateChanged; + _realtimeClient.SessionConnected += HandleSessionConnected; + _realtimeClient.MessageCreated += HandleMessageCreated; + _realtimeClient.ReadCursorUpdated += HandleReadCursorUpdated; + _conversationWindowManager.WindowCountChanged += HandleDetachedWindowCountChanged; + } + + public ObservableCollection Conversations { get; } = []; + public ObservableCollection FilteredConversations { get; } = []; + public ObservableCollection Messages { get; } = []; + + public IAsyncRelayCommand SignInCommand { get; } + public IAsyncRelayCommand SendMessageCommand { get; } + public IAsyncRelayCommand SignOutCommand { get; } + public IAsyncRelayCommand ReloadCommand { get; } + public IAsyncRelayCommand DetachConversationCommand { get; } + public IAsyncRelayCommand DetachConversationRowCommand { get; } + public IRelayCommand ToggleAdvancedSettingsCommand { get; } + public IRelayCommand ShowAllConversationsCommand { get; } + public IRelayCommand ShowUnreadConversationsCommand { get; } + public IRelayCommand ShowPinnedConversationsCommand { get; } + public IRelayCommand ToggleCompactModeCommand { get; } + public IRelayCommand ToggleInspectorCommand { get; } + public IRelayCommand ToggleConversationPaneCommand { get; } + public IRelayCommand ResetWorkspaceCommand { get; } + public IRelayCommand SelectConversationCommand { get; } + + [ObservableProperty] private string apiBaseUrl = DefaultApiBaseUrl; + [ObservableProperty] private string displayName = string.Empty; + [ObservableProperty] private string inviteCode = string.Empty; + [ObservableProperty] private bool rememberSession = true; + [ObservableProperty] private bool showAdvancedSettings; + [ObservableProperty] private bool isAuthenticated; + [ObservableProperty] private bool isBusy; + [ObservableProperty] private string currentUserDisplayName = "KO"; + [ObservableProperty] private string statusLine = string.Empty; + [ObservableProperty] private RealtimeConnectionState realtimeState = RealtimeConnectionState.Idle; + [ObservableProperty] private string realtimeStatusText = "준비"; + [ObservableProperty] private string? errorText; + [ObservableProperty] private string conversationSearchText = string.Empty; + [ObservableProperty] private string selectedListFilter = "all"; + [ObservableProperty] private string composerText = string.Empty; + [ObservableProperty] private string selectedConversationTitle = "KoTalk"; + [ObservableProperty] private string selectedConversationSubtitle = "대화를 열면 바로 이어집니다."; + [ObservableProperty] private ConversationRowViewModel? selectedConversation; + [ObservableProperty] private bool hasErrorText; + [ObservableProperty] private bool hasFilteredConversations; + [ObservableProperty] private string conversationEmptyStateText = "지금 표시할 대화가 없습니다."; + [ObservableProperty] private bool isCompactDensity = true; + [ObservableProperty] private bool isInspectorVisible; + [ObservableProperty] private bool isConversationPaneCollapsed; + [ObservableProperty] private int detachedWindowCount; + + public bool ShowOnboarding => !IsAuthenticated; + public bool ShowShell => IsAuthenticated; + public bool IsAllFilterSelected => string.Equals(SelectedListFilter, "all", StringComparison.Ordinal); + public bool IsUnreadFilterSelected => string.Equals(SelectedListFilter, "unread", StringComparison.Ordinal); + public bool IsPinnedFilterSelected => string.Equals(SelectedListFilter, "pinned", StringComparison.Ordinal); + public int TotalConversationCount => Conversations.Count; + public int UnreadConversationCount => Conversations.Count(item => item.UnreadCount > 0); + public int PinnedConversationCount => Conversations.Count(item => item.IsPinned); + public bool ShowConversationEmptyState => !HasFilteredConversations; + public string AdvancedSettingsButtonText => ShowAdvancedSettings ? "기본" : "고급"; + public string CurrentUserMonogram => + string.IsNullOrWhiteSpace(CurrentUserDisplayName) ? "KO" : CurrentUserDisplayName.Trim()[..Math.Min(2, CurrentUserDisplayName.Trim().Length)]; + public string AllFilterButtonText => "◎"; + public string UnreadFilterButtonText => "●"; + public string PinnedFilterButtonText => "★"; + public string RealtimeStatusGlyph => RealtimeState switch + { + RealtimeConnectionState.Connected => "●", + RealtimeConnectionState.Connecting => "◌", + RealtimeConnectionState.Reconnecting => "◔", + RealtimeConnectionState.Disconnected => "○", + _ => "·" + }; + public string CompactModeGlyph => IsCompactDensity ? "◫" : "◻"; + public string DensityGlyph => IsCompactDensity ? "▥" : "▤"; + public string InspectorGlyph => IsInspectorVisible ? "▣" : "□"; + public string InspectorActionGlyph => IsInspectorVisible ? "▣" : "□"; + public string ConversationPaneGlyph => IsConversationPaneCollapsed ? "›" : "‹"; + public string PaneActionGlyph => IsConversationPaneCollapsed ? "›" : "‹"; + public string SelectedConversationGlyph => + SelectedConversation is null ? "KO" : SelectedConversation.AvatarText; + public bool HasSelectedConversation => SelectedConversation is not null; + public bool HasSelectedConversationUnread => (SelectedConversation?.UnreadCount ?? 0) > 0; + public string SelectedConversationUnreadBadgeText => (SelectedConversation?.UnreadCount ?? 0) > 99 + ? "99+" + : (SelectedConversation?.UnreadCount ?? 0).ToString(); + public bool SelectedConversationIsPinned => SelectedConversation?.IsPinned ?? false; + public string DetachedWindowBadgeText => DetachedWindowCount > 9 ? "9+" : DetachedWindowCount.ToString(); + public string DetachedWindowActionGlyph => HasDetachedWindows ? DetachedWindowBadgeText : "↗"; + public bool HasDetachedWindows => DetachedWindowCount > 0; + public bool IsConversationPaneExpanded => !IsConversationPaneCollapsed; + public double ConversationPaneWidth => IsConversationPaneCollapsed ? 0 : (IsCompactDensity ? 296 : 340); + public double InspectorPaneWidth => IsInspectorVisible ? (IsCompactDensity ? 92 : 108) : 0; + public Thickness ConversationRowPadding => IsCompactDensity ? new Thickness(6, 6) : new Thickness(8, 7); + public Thickness MessageBubblePadding => IsCompactDensity ? new Thickness(10, 8) : new Thickness(12, 10); + public double ConversationAvatarSize => IsCompactDensity ? 26 : 30; + public double ComposerMinHeight => IsCompactDensity ? 48 : 58; + public string ComposerCounterText => $"{ComposerText.Trim().Length}"; + public string SearchWatermark => "대화 검색"; + public string InspectorStatusText => HasDetachedWindows + ? $"{RealtimeStatusGlyph} {DetachedWindowBadgeText}" + : RealtimeStatusGlyph; + public string WorkspaceModeText => HasDetachedWindows ? $"분리 창 {DetachedWindowBadgeText}" : "단일 창"; + public string StatusSummaryText => string.IsNullOrWhiteSpace(StatusLine) ? RealtimeStatusText : StatusLine; + public string ComposerPlaceholderText => HasSelectedConversation ? "메시지 보내기" : "왼쪽에서 대화를 고르세요"; + public string ComposerActionText => Messages.Count == 0 ? "시작" : "보내기"; + public bool ShowMessageEmptyState => Messages.Count == 0; + public string MessageEmptyStateTitle => HasSelectedConversation ? "첫 메시지부터 시작" : "대화를 먼저 고르세요"; + public string MessageEmptyStateText => HasSelectedConversation + ? "짧게 한 줄만 남겨도 바로 이어집니다." + : "받은함에서 대화를 고르거나 창으로 분리해 집중할 수 있습니다."; + + public async Task InitializeAsync() + { + if (string.Equals(Environment.GetEnvironmentVariable("KOTALK_DESKTOP_SAMPLE_MODE"), "1", StringComparison.Ordinal)) + { + LoadSampleWorkspace(); + return; + } + + var workspaceLayout = await _workspaceLayoutStore.LoadAsync(); + if (workspaceLayout is not null) + { + ApplyWorkspaceLayout(workspaceLayout); + } + + var storedSession = await _sessionStore.LoadAsync(); + if (storedSession is null) + { + return; + } + + ApiBaseUrl = storedSession.ApiBaseUrl; + _session = storedSession; + await RestoreSessionAsync(storedSession); + } + + public async Task SendMessageFromShortcutAsync() + { + if (SendMessageCommand.CanExecute(null)) + { + await SendMessageCommand.ExecuteAsync(null); + } + } + + public async Task OpenDetachedConversationFromShortcutAsync() + { + if (DetachConversationCommand.CanExecute(null)) + { + await DetachConversationCommand.ExecuteAsync(null); + } + } + + partial void OnIsAuthenticatedChanged(bool value) + { + OnPropertyChanged(nameof(ShowOnboarding)); + OnPropertyChanged(nameof(ShowShell)); + ReloadCommand.NotifyCanExecuteChanged(); + SendMessageCommand.NotifyCanExecuteChanged(); + DetachConversationCommand.NotifyCanExecuteChanged(); + DetachConversationRowCommand.NotifyCanExecuteChanged(); + } + + partial void OnApiBaseUrlChanged(string value) => SignInCommand.NotifyCanExecuteChanged(); + partial void OnDisplayNameChanged(string value) => SignInCommand.NotifyCanExecuteChanged(); + partial void OnInviteCodeChanged(string value) => SignInCommand.NotifyCanExecuteChanged(); + partial void OnShowAdvancedSettingsChanged(bool value) => OnPropertyChanged(nameof(AdvancedSettingsButtonText)); + partial void OnConversationSearchTextChanged(string value) => RefreshConversationFilter(); + partial void OnSelectedListFilterChanged(string value) + { + OnPropertyChanged(nameof(IsAllFilterSelected)); + OnPropertyChanged(nameof(IsUnreadFilterSelected)); + OnPropertyChanged(nameof(IsPinnedFilterSelected)); + RefreshConversationFilter(); + } + partial void OnErrorTextChanged(string? value) => HasErrorText = !string.IsNullOrWhiteSpace(value); + partial void OnHasFilteredConversationsChanged(bool value) => OnPropertyChanged(nameof(ShowConversationEmptyState)); + partial void OnStatusLineChanged(string value) => OnPropertyChanged(nameof(StatusSummaryText)); + partial void OnComposerTextChanged(string value) + { + SendMessageCommand.NotifyCanExecuteChanged(); + OnPropertyChanged(nameof(ComposerCounterText)); + } + partial void OnRealtimeStatusTextChanged(string value) => OnPropertyChanged(nameof(StatusSummaryText)); + partial void OnRealtimeStateChanged(RealtimeConnectionState value) + { + OnPropertyChanged(nameof(RealtimeStatusGlyph)); + OnPropertyChanged(nameof(InspectorStatusText)); + } + partial void OnIsCompactDensityChanged(bool value) + { + OnPropertyChanged(nameof(CompactModeGlyph)); + OnPropertyChanged(nameof(DensityGlyph)); + OnPropertyChanged(nameof(ConversationPaneWidth)); + OnPropertyChanged(nameof(InspectorPaneWidth)); + OnPropertyChanged(nameof(ConversationRowPadding)); + OnPropertyChanged(nameof(MessageBubblePadding)); + OnPropertyChanged(nameof(ConversationAvatarSize)); + OnPropertyChanged(nameof(ComposerMinHeight)); + _ = PersistWorkspaceLayoutAsync(); + } + partial void OnIsInspectorVisibleChanged(bool value) + { + OnPropertyChanged(nameof(InspectorGlyph)); + OnPropertyChanged(nameof(InspectorActionGlyph)); + OnPropertyChanged(nameof(InspectorPaneWidth)); + _ = PersistWorkspaceLayoutAsync(); + } + partial void OnIsConversationPaneCollapsedChanged(bool value) + { + OnPropertyChanged(nameof(ConversationPaneGlyph)); + OnPropertyChanged(nameof(PaneActionGlyph)); + OnPropertyChanged(nameof(IsConversationPaneExpanded)); + OnPropertyChanged(nameof(ConversationPaneWidth)); + OnPropertyChanged(nameof(SearchWatermark)); + _ = PersistWorkspaceLayoutAsync(); + } + partial void OnDetachedWindowCountChanged(int value) + { + OnPropertyChanged(nameof(DetachedWindowBadgeText)); + OnPropertyChanged(nameof(DetachedWindowActionGlyph)); + OnPropertyChanged(nameof(HasDetachedWindows)); + OnPropertyChanged(nameof(InspectorStatusText)); + OnPropertyChanged(nameof(WorkspaceModeText)); + } + + partial void OnSelectedConversationChanged(ConversationRowViewModel? value) + { + UpdateSelectedConversationState(value?.ConversationId); + SelectedConversationTitle = value?.Title ?? "KoTalk"; + SelectedConversationSubtitle = value?.Subtitle ?? "받은함과 대화를 한 화면에서 관리합니다."; + OnPropertyChanged(nameof(SelectedConversationGlyph)); + OnPropertyChanged(nameof(HasSelectedConversation)); + OnPropertyChanged(nameof(HasSelectedConversationUnread)); + OnPropertyChanged(nameof(SelectedConversationUnreadBadgeText)); + OnPropertyChanged(nameof(SelectedConversationIsPinned)); + OnPropertyChanged(nameof(ComposerPlaceholderText)); + NotifyMessageStateChanged(); + SendMessageCommand.NotifyCanExecuteChanged(); + DetachConversationCommand.NotifyCanExecuteChanged(); + _ = HandleSelectedConversationChangedAsync(value); + } + + private async Task SignInAsync() + { + await RunBusyAsync(async () => + { + var apiBaseUrl = ResolveApiBaseUrl(); + var request = new RegisterAlphaQuickRequest( + DisplayName.Trim(), + InviteCode.Trim(), + new DeviceRegistrationDto( + $"desktop-{Environment.MachineName.ToLowerInvariant()}", + "windows", + Environment.MachineName, + "0.1.0")); + + var response = await _apiClient.RegisterAlphaQuickAsync(apiBaseUrl, request, CancellationToken.None); + ApiBaseUrl = apiBaseUrl; + _session = new DesktopSession( + apiBaseUrl, + response.Tokens.AccessToken, + response.Tokens.RefreshToken, + response.Account.DisplayName, + response.Bootstrap.Conversations.Items.FirstOrDefault()?.ConversationId); + + if (RememberSession) + { + await _sessionStore.SaveAsync(_session); + } + + ApplyBootstrap(response.Bootstrap, response.Account.DisplayName, _session.LastConversationId); + await StartRealtimeAsync(response.Bootstrap.Ws.Url, _session.AccessToken); + StatusLine = "준비"; + NotifyMessageStateChanged(); + }); + } + + private async Task ReloadAsync() + { + if (_session is null) + { + return; + } + + await RestoreSessionAsync(_session); + } + + private async Task SignOutAsync() + { + await _realtimeClient.DisconnectAsync(); + _session = null; + _currentUserId = null; + await _sessionStore.ClearAsync(); + Conversations.Clear(); + FilteredConversations.Clear(); + Messages.Clear(); + IsAuthenticated = false; + CurrentUserDisplayName = "KO"; + StatusLine = string.Empty; + RealtimeState = RealtimeConnectionState.Idle; + RealtimeStatusText = "준비"; + ErrorText = null; + ApiBaseUrl = DefaultApiBaseUrl; + ConversationSearchText = string.Empty; + SelectedListFilter = "all"; + SelectedConversation = null; + SelectedConversationTitle = "KoTalk"; + SelectedConversationSubtitle = "대화를 열면 바로 이어집니다."; + NotifyConversationMetricsChanged(); + } + + private async Task HandleSelectedConversationChangedAsync(ConversationRowViewModel? value) + { + if (!IsAuthenticated || value is null || _session is null) + { + return; + } + + await RunBusyAsync(async () => + { + var items = await _apiClient.GetMessagesAsync(_session.ApiBaseUrl, _session.AccessToken, value.ConversationId, CancellationToken.None); + + Messages.Clear(); + foreach (var item in items.Items) + { + Messages.Add(MapMessage(item)); + } + + if (Messages.Count > 0) + { + var lastSequence = Messages[^1].ServerSequence; + value.LastReadSequence = lastSequence; + value.UnreadCount = 0; + await _apiClient.UpdateReadCursorAsync( + _session.ApiBaseUrl, + _session.AccessToken, + value.ConversationId, + new UpdateReadCursorRequest(lastSequence), + CancellationToken.None); + } + + NotifyConversationMetricsChanged(); + NotifyMessageStateChanged(); + RefreshConversationFilter(value.ConversationId); + + if (_session is not null) + { + _session = _session with { LastConversationId = value.ConversationId }; + if (RememberSession) + { + await _sessionStore.SaveAsync(_session); + } + } + }, clearMessages: false); + } + + private async Task SendMessageAsync() + { + if (_session is null || SelectedConversation is null || string.IsNullOrWhiteSpace(ComposerText)) + { + return; + } + + var draft = ComposerText.Trim(); + var clientMessageId = Guid.NewGuid(); + ComposerText = string.Empty; + + var pending = new MessageRowViewModel + { + MessageId = $"pending-{Guid.NewGuid():N}", + ClientMessageId = clientMessageId, + Text = draft, + SenderName = CurrentUserDisplayName, + MetaText = "전송 중", + IsMine = true, + IsPending = true, + ServerSequence = Messages.Count == 0 ? 1 : Messages[^1].ServerSequence + 1 + }; + + Messages.Add(pending); + NotifyMessageStateChanged(); + + try + { + var sent = await _apiClient.SendTextMessageAsync( + _session.ApiBaseUrl, + _session.AccessToken, + SelectedConversation.ConversationId, + new PostTextMessageRequest(clientMessageId, draft), + CancellationToken.None); + + Messages.Remove(pending); + var committed = MapMessage(sent); + UpsertMessage(committed); + UpdateConversationAfterMessage(sent); + StatusLine = "전송"; + NotifyMessageStateChanged(); + } + catch (Exception) + { + pending.IsPending = false; + pending.IsFailed = true; + pending.MetaText = "실패"; + ErrorText = "메시지를 보내지 못했습니다."; + NotifyMessageStateChanged(); + } + } + + private async Task RestoreSessionAsync(DesktopSession session) + { + await RunBusyAsync(async () => + { + var bootstrap = await _apiClient.GetBootstrapAsync(session.ApiBaseUrl, session.AccessToken, CancellationToken.None); + _session = session; + ApplyBootstrap(bootstrap, session.DisplayName, session.LastConversationId); + await StartRealtimeAsync(bootstrap.Ws.Url, session.AccessToken); + StatusLine = "복원"; + NotifyMessageStateChanged(); + }); + } + + private async Task DetachConversationAsync() + { + if (_session is null || SelectedConversation is null) + { + return; + } + + await ShowDetachedConversationAsync(SelectedConversation); + StatusLine = "분리"; + } + + private async Task DetachConversationRowAsync(ConversationRowViewModel? conversation) + { + if (conversation is null || !CanDetachConversationRow(conversation)) + { + return; + } + + SelectedConversation = conversation; + await ShowDetachedConversationAsync(conversation); + StatusLine = "분리"; + } + + private void ApplyBootstrap(BootstrapResponse bootstrap, string displayName, string? preferredConversationId) + { + _currentUserId = bootstrap.Me.UserId; + CurrentUserDisplayName = displayName; + Conversations.Clear(); + + foreach (var item in bootstrap.Conversations.Items) + { + Conversations.Add(new ConversationRowViewModel + { + ConversationId = item.ConversationId, + Title = item.Title, + Subtitle = item.Subtitle, + LastMessageText = item.LastMessage?.Text ?? string.Empty, + MetaText = FormatConversationMeta(item), + UnreadCount = item.UnreadCount, + IsPinned = item.IsPinned, + LastReadSequence = item.LastReadSequence, + SortKey = item.SortKey + }); + } + + IsAuthenticated = true; + ErrorText = null; + OnPropertyChanged(nameof(CurrentUserMonogram)); + NotifyConversationMetricsChanged(); + NotifyMessageStateChanged(); + RefreshConversationFilter(preferredConversationId); + + var target = FilteredConversations.FirstOrDefault(x => x.ConversationId == preferredConversationId) + ?? FilteredConversations.FirstOrDefault() + ?? Conversations.FirstOrDefault(); + + if (target is not null) + { + SelectedConversation = target; + } + } + + private bool CanSignIn() => + !IsBusy && + !string.IsNullOrWhiteSpace(DisplayName) && + !string.IsNullOrWhiteSpace(InviteCode); + + private bool CanSendMessage() => + !IsBusy && + IsAuthenticated && + SelectedConversation is not null && + !string.IsNullOrWhiteSpace(ComposerText); + + private bool CanDetachConversation() => + !IsBusy && + IsAuthenticated && + SelectedConversation is not null; + + private bool CanDetachConversationRow(ConversationRowViewModel? conversation) => + !IsBusy && + IsAuthenticated && + conversation is not null; + + private string ResolveApiBaseUrl() + { + var trimmed = ApiBaseUrl.Trim(); + return string.IsNullOrWhiteSpace(trimmed) ? DefaultApiBaseUrl : trimmed; + } + + private async Task ShowDetachedConversationAsync(ConversationRowViewModel conversation) + { + if (_session is null) + { + return; + } + + await _conversationWindowManager.ShowOrFocusAsync(new ConversationWindowLaunch( + _session.ApiBaseUrl, + _session.AccessToken, + CurrentUserDisplayName, + conversation.ConversationId, + conversation.Title, + conversation.Subtitle)); + } + + private void RefreshConversationFilter(string? preferredConversationId = null) + { + var search = ConversationSearchText.Trim(); + var filtered = Conversations + .Where(PassesSelectedFilter) + .Where(item => string.IsNullOrWhiteSpace(search) || MatchesConversationSearch(item, search)) + .ToList(); + + FilteredConversations.Clear(); + foreach (var item in filtered) + { + FilteredConversations.Add(item); + } + + HasFilteredConversations = filtered.Count > 0; + ConversationEmptyStateText = string.IsNullOrWhiteSpace(search) + ? (SelectedListFilter switch + { + "unread" => "안읽음 대화가 없습니다.", + "pinned" => "고정한 대화가 없습니다.", + _ => "받은함이 비어 있습니다." + }) + : "검색 결과가 없습니다."; + + var targetId = preferredConversationId ?? SelectedConversation?.ConversationId; + var target = !string.IsNullOrWhiteSpace(targetId) + ? FilteredConversations.FirstOrDefault(item => item.ConversationId == targetId) + : null; + + if (target is null) + { + target = FilteredConversations.FirstOrDefault(); + } + + if (!ReferenceEquals(SelectedConversation, target)) + { + SelectedConversation = target; + } + else + { + UpdateSelectedConversationState(target?.ConversationId); + NotifyMessageStateChanged(); + } + } + + private bool PassesSelectedFilter(ConversationRowViewModel item) + { + return SelectedListFilter switch + { + "unread" => item.UnreadCount > 0, + "pinned" => item.IsPinned, + _ => true + }; + } + + private static bool MatchesConversationSearch(ConversationRowViewModel item, string search) + { + return item.Title.Contains(search, StringComparison.CurrentCultureIgnoreCase) || + item.LastMessageText.Contains(search, StringComparison.CurrentCultureIgnoreCase) || + item.Subtitle.Contains(search, StringComparison.CurrentCultureIgnoreCase); + } + + private void UpdateSelectedConversationState(string? conversationId) + { + foreach (var item in Conversations) + { + item.IsSelected = !string.IsNullOrWhiteSpace(conversationId) && + string.Equals(item.ConversationId, conversationId, StringComparison.Ordinal); + } + } + + private void NotifyConversationMetricsChanged() + { + OnPropertyChanged(nameof(TotalConversationCount)); + OnPropertyChanged(nameof(UnreadConversationCount)); + OnPropertyChanged(nameof(PinnedConversationCount)); + } + + private void LoadSampleWorkspace() + { + Conversations.Clear(); + FilteredConversations.Clear(); + Messages.Clear(); + + CurrentUserDisplayName = "이안"; + DisplayName = "이안"; + InviteCode = string.Empty; + RealtimeState = RealtimeConnectionState.Connected; + RealtimeStatusText = "연결됨"; + StatusLine = "준비"; + IsAuthenticated = true; + IsCompactDensity = true; + IsInspectorVisible = false; + IsConversationPaneCollapsed = false; + DetachedWindowCount = 1; + ErrorText = null; + + var now = DateTimeOffset.Now; + Conversations.Add(new ConversationRowViewModel + { + ConversationId = "sample-ops", + Title = "제품 운영", + Subtitle = "스크린샷 기준으로 레이아웃을 다시 정리했습니다.", + LastMessageText = "스크린샷 기준으로 레이아웃을 다시 정리했습니다.", + MetaText = FormatConversationMeta(now.AddMinutes(-5), 2), + UnreadCount = 2, + IsPinned = true, + LastReadSequence = 12, + SortKey = now.AddMinutes(-5) + }); + Conversations.Add(new ConversationRowViewModel + { + ConversationId = "sample-review", + Title = "디자인 리뷰", + Subtitle = "오후 2시에 검수 포인트만 다시 볼게요.", + LastMessageText = "오후 2시에 검수 포인트만 다시 볼게요.", + MetaText = FormatConversationMeta(now.AddMinutes(-22), 0), + UnreadCount = 0, + IsPinned = false, + LastReadSequence = 5, + SortKey = now.AddMinutes(-22) + }); + Conversations.Add(new ConversationRowViewModel + { + ConversationId = "sample-friends", + Title = "주말 약속", + Subtitle = "토요일 브런치 장소만 정하면 끝.", + LastMessageText = "토요일 브런치 장소만 정하면 끝.", + MetaText = FormatConversationMeta(now.AddMinutes(-54), 0), + UnreadCount = 0, + IsPinned = false, + LastReadSequence = 3, + SortKey = now.AddMinutes(-54) + }); + + NotifyConversationMetricsChanged(); + RefreshConversationFilter("sample-ops"); + + SelectedConversation = Conversations.FirstOrDefault(item => item.ConversationId == "sample-ops"); + Messages.Clear(); + foreach (var item in new[] + { + new MessageRowViewModel + { + MessageId = "sample-msg-1", + SenderName = "민지", + Text = "회의 전에 이슈만 짧게 정리해 주세요.", + MetaText = "08:54", + IsMine = false, + ServerSequence = 13 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-2", + SenderName = "이안", + Text = "레이아웃 구조를 다시 줄였습니다. 우측 빈 패널도 없앴어요.", + MetaText = "08:56", + IsMine = true, + ServerSequence = 14 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-3", + SenderName = "민지", + Text = "좋아요. 지금 화면이면 바로 검수할 수 있겠네요.", + MetaText = "08:58", + IsMine = false, + ServerSequence = 15 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-4", + SenderName = "이안", + Text = "스크린샷 기준으로 레이아웃도 바로 수정했습니다.", + MetaText = "09:05", + IsMine = true, + ServerSequence = 16 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-5", + SenderName = "민지", + Text = "좋아요. 바로 확인 가능한 흐름으로 정리됐어요.", + MetaText = "09:06", + IsMine = false, + ServerSequence = 17 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-6", + SenderName = "이안", + Text = "분리 창도 한 번에 열리도록 남겨 두었습니다.", + MetaText = "09:07", + IsMine = true, + ServerSequence = 18 + }, + new MessageRowViewModel + { + MessageId = "sample-msg-7", + SenderName = "민지", + Text = "이 정도면 데스크톱 검수용 화면으로 충분하겠네요.", + MetaText = "09:08", + IsMine = false, + ServerSequence = 19 + } + }) + { + Messages.Add(item); + } + + OnPropertyChanged(nameof(CurrentUserMonogram)); + NotifyMessageStateChanged(); + } + + private void HandleMessagesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + NotifyMessageStateChanged(); + } + + private void NotifyMessageStateChanged() + { + OnPropertyChanged(nameof(ShowMessageEmptyState)); + OnPropertyChanged(nameof(MessageEmptyStateTitle)); + OnPropertyChanged(nameof(MessageEmptyStateText)); + OnPropertyChanged(nameof(ComposerPlaceholderText)); + OnPropertyChanged(nameof(ComposerActionText)); + } + + private async Task StartRealtimeAsync(string wsUrl, string accessToken) + { + await _realtimeClient.ConnectAsync(wsUrl, accessToken, CancellationToken.None); + } + + private async Task RunBusyAsync(Func action, bool clearMessages = true) + { + if (IsBusy) + { + return; + } + + try + { + IsBusy = true; + ErrorText = null; + if (clearMessages) + { + StatusLine = "동기화"; + } + await action(); + } + catch (Exception exception) + { + ErrorText = exception.Message; + if (clearMessages) + { + Messages.Clear(); + } + } + finally + { + IsBusy = false; + SignInCommand.NotifyCanExecuteChanged(); + SendMessageCommand.NotifyCanExecuteChanged(); + ReloadCommand.NotifyCanExecuteChanged(); + DetachConversationCommand.NotifyCanExecuteChanged(); + DetachConversationRowCommand.NotifyCanExecuteChanged(); + } + } + + private static MessageRowViewModel MapMessage(MessageItemDto item) + { + return new MessageRowViewModel + { + MessageId = item.MessageId, + ClientMessageId = item.ClientMessageId, + Text = item.Text, + SenderName = item.Sender.DisplayName, + MetaText = $"{item.CreatedAt.LocalDateTime:HH:mm}", + IsMine = item.IsMine, + ServerSequence = item.ServerSequence + }; + } + + private static string FormatConversationMeta(ConversationSummaryDto item) + { + return FormatConversationMeta(item.LastMessage?.CreatedAt ?? item.SortKey, item.UnreadCount); + } + + private static string FormatConversationMeta(DateTimeOffset timestamp, int unreadCount) + { + var timeText = timestamp.LocalDateTime.ToString("HH:mm"); + return unreadCount > 0 ? $"{timeText} · {unreadCount}" : timeText; + } + + private void HandleRealtimeConnectionStateChanged(RealtimeConnectionState state) + { + Dispatcher.UIThread.Post(() => + { + RealtimeState = state; + RealtimeStatusText = state switch + { + RealtimeConnectionState.Connecting => "동기화", + RealtimeConnectionState.Connected => "연결됨", + RealtimeConnectionState.Reconnecting => "다시 연결", + RealtimeConnectionState.Disconnected => "오프라인", + _ => "준비" + }; + }); + } + + private void HandleSessionConnected(SessionConnectedDto payload) + { + Dispatcher.UIThread.Post(() => + { + RealtimeState = RealtimeConnectionState.Connected; + RealtimeStatusText = "연결됨"; + }); + } + + private void HandleMessageCreated(MessageItemDto payload) + { + Dispatcher.UIThread.Post(() => + { + UpdateConversationAfterMessage(payload); + + if (SelectedConversation?.ConversationId == payload.ConversationId) + { + UpsertMessage(MapMessage(payload)); + } + }); + } + + private void HandleReadCursorUpdated(ReadCursorUpdatedDto payload) + { + Dispatcher.UIThread.Post(() => + { + if (_currentUserId is null || !string.Equals(payload.AccountId, _currentUserId, StringComparison.OrdinalIgnoreCase)) + { + return; + } + + var conversation = Conversations.FirstOrDefault(item => item.ConversationId == payload.ConversationId); + if (conversation is null) + { + return; + } + + conversation.LastReadSequence = payload.LastReadSequence; + conversation.UnreadCount = 0; + conversation.MetaText = FormatConversationMeta(conversation.SortKey, 0); + NotifyConversationMetricsChanged(); + RefreshConversationFilter(conversation.ConversationId); + }); + } + + private void HandleDetachedWindowCountChanged(int count) + { + Dispatcher.UIThread.Post(() => DetachedWindowCount = count); + } + + private void UpdateConversationAfterMessage(MessageItemDto payload) + { + var conversation = Conversations.FirstOrDefault(item => item.ConversationId == payload.ConversationId); + if (conversation is null) + { + return; + } + + conversation.LastMessageText = payload.Text; + conversation.SortKey = payload.CreatedAt; + conversation.LastReadSequence = payload.IsMine + ? Math.Max(conversation.LastReadSequence, payload.ServerSequence) + : conversation.LastReadSequence; + conversation.UnreadCount = payload.IsMine + ? 0 + : (SelectedConversation?.ConversationId == payload.ConversationId + ? conversation.UnreadCount + : Math.Max(conversation.UnreadCount + 1, 1)); + conversation.MetaText = FormatConversationMeta(conversation.SortKey, conversation.UnreadCount); + NotifyConversationMetricsChanged(); + ReorderConversations(conversation.ConversationId); + } + + private void UpsertMessage(MessageRowViewModel next) + { + var items = Messages.ToList(); + var existingIndex = items.FindIndex(item => + string.Equals(item.MessageId, next.MessageId, StringComparison.Ordinal) || + (next.ClientMessageId != Guid.Empty && item.ClientMessageId == next.ClientMessageId)); + + if (existingIndex >= 0) + { + items[existingIndex] = next; + } + else + { + items.Add(next); + } + + var ordered = items + .OrderBy(item => item.ServerSequence) + .ThenBy(item => item.IsPending ? 1 : 0) + .ToList(); + + Messages.Clear(); + foreach (var item in ordered) + { + Messages.Add(item); + } + } + + private void ReorderConversations(string? selectedConversationId) + { + var ordered = Conversations + .OrderByDescending(item => item.IsPinned) + .ThenByDescending(item => item.SortKey) + .ToList(); + + Conversations.Clear(); + foreach (var item in ordered) + { + Conversations.Add(item); + } + + RefreshConversationFilter(selectedConversationId); + } + + private void ResetWorkspaceLayout() + { + IsCompactDensity = true; + IsInspectorVisible = false; + IsConversationPaneCollapsed = false; + StatusLine = "초기화"; + } + + private void ApplyWorkspaceLayout(DesktopWorkspaceLayout layout) + { + IsCompactDensity = layout.IsCompactDensity; + IsInspectorVisible = layout.IsInspectorVisible; + IsConversationPaneCollapsed = layout.IsConversationPaneCollapsed; + } + + private Task PersistWorkspaceLayoutAsync() + { + return _workspaceLayoutStore.SaveAsync(new DesktopWorkspaceLayout( + IsCompactDensity, + IsInspectorVisible, + IsConversationPaneCollapsed)); + } + + public async ValueTask DisposeAsync() + { + Messages.CollectionChanged -= HandleMessagesCollectionChanged; + _conversationWindowManager.WindowCountChanged -= HandleDetachedWindowCountChanged; + _realtimeClient.ConnectionStateChanged -= HandleRealtimeConnectionStateChanged; + _realtimeClient.SessionConnected -= HandleSessionConnected; + _realtimeClient.MessageCreated -= HandleMessageCreated; + _realtimeClient.ReadCursorUpdated -= HandleReadCursorUpdated; + await _realtimeClient.DisposeAsync(); + } +} diff --git a/src/PhysOn.Desktop/ViewModels/MessageRowViewModel.cs b/src/PhysOn.Desktop/ViewModels/MessageRowViewModel.cs new file mode 100644 index 0000000..a85f396 --- /dev/null +++ b/src/PhysOn.Desktop/ViewModels/MessageRowViewModel.cs @@ -0,0 +1,23 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace PhysOn.Desktop.ViewModels; + +public partial class MessageRowViewModel : ViewModelBase +{ + [ObservableProperty] private string messageId = string.Empty; + [ObservableProperty] private Guid clientMessageId; + [ObservableProperty] private string text = string.Empty; + [ObservableProperty] private string senderName = string.Empty; + [ObservableProperty] private string metaText = string.Empty; + [ObservableProperty] private bool isMine; + [ObservableProperty] private bool isPending; + [ObservableProperty] private bool isFailed; + [ObservableProperty] private long serverSequence; + + public bool ShowSenderName => !IsMine; + + partial void OnIsMineChanged(bool value) + { + OnPropertyChanged(nameof(ShowSenderName)); + } +} diff --git a/src/PhysOn.Desktop/ViewModels/ViewModelBase.cs b/src/PhysOn.Desktop/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..3e957ea --- /dev/null +++ b/src/PhysOn.Desktop/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace PhysOn.Desktop.ViewModels; + +public abstract class ViewModelBase : ObservableObject +{ +} diff --git a/src/PhysOn.Desktop/Views/ConversationWindow.axaml b/src/PhysOn.Desktop/Views/ConversationWindow.axaml new file mode 100644 index 0000000..f15305e --- /dev/null +++ b/src/PhysOn.Desktop/Views/ConversationWindow.axaml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +