🚚 feat(context): integrate context app into monorepo

Restructure the context app (formerly basetext) to follow the monorepo
pattern with proper workspace configuration.

Changes:
- Move app files to apps/context/apps/mobile/
- Rename package to @context/mobile
- Update bundle ID to com.manacore.context
- Create pnpm-workspace.yaml for project workspace
- Add dev scripts to root package.json
- Update CLAUDE.md with project documentation

The app structure is prepared for future web/backend additions.

Note: Existing TypeScript errors in the original codebase are preserved.
These should be fixed in a follow-up PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-05 15:09:04 +01:00
parent 34c879929b
commit bb0e0cf5cb
303 changed files with 31904 additions and 475 deletions

View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,9 +0,0 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

View file

@ -1,16 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/app.css"
}

View file

@ -0,0 +1,44 @@
{
"name": "@worldream/web",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"type-check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/kit": "^2.22.0",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^7.0.4"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@manacore/shared-auth": "workspace:*",
"@manacore/shared-auth-ui": "workspace:*",
"@manacore/shared-branding": "workspace:*",
"@manacore/shared-i18n": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-utils": "workspace:*",
"@paralleldrive/cuid2": "^3.0.4",
"@supabase/ssr": "^0.7.0",
"@supabase/supabase-js": "^2.56.1",
"@worldream/types": "workspace:*",
"marked": "^16.2.1",
"openai": "^5.16.0",
"replicate": "^1.1.0"
}
}

View file

@ -6,12 +6,11 @@ declare global {
namespace App {
interface Locals {
supabase: SupabaseClient;
// Session helpers - returns mock session while transitioning to Mana Core Auth
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>;
getSession: () => Promise<Session | null>;
}
interface PageData {
session: Session | null;
user: User | null;
}
// interface PageData {}
// interface Error {}
// interface PageState {}
// interface Platform {}

View file

@ -0,0 +1,41 @@
/**
* Server Hooks for SvelteKit
* Auth is handled client-side via Mana Core Auth
* Supabase is still used for database operations
*
* TODO: Migrate API routes to use Mana Core Auth headers instead of session-based auth
*/
import { createClient } from '$lib/supabase/server';
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// Create Supabase client for database operations
event.locals.supabase = createClient(event);
// Provide session helpers for backwards compatibility
// These are stubs while transitioning to Mana Core Auth
event.locals.safeGetSession = async () => {
// In the future, this should validate the Mana Core Auth token
// For now, return a mock session for development
const {
data: { session },
error,
} = await event.locals.supabase.auth.getSession();
if (error || !session) {
return { session: null, user: null };
}
return { session, user: session.user };
};
event.locals.getSession = async () => {
const { session } = await event.locals.safeGetSession();
return session;
};
return resolve(event, {
filterSerializedResponseHeaders(name) {
return name === 'content-range' || name === 'x-supabase-api-version';
},
});
};

View file

@ -233,7 +233,9 @@ function validateAndCleanUpdates(updates: any, originalNode: ContentNode): Parti
}
if (updates.tags && Array.isArray(updates.tags)) {
cleaned.tags = updates.tags.filter((tag) => typeof tag === 'string').map((tag) => tag.trim());
cleaned.tags = updates.tags
.filter((tag: unknown): tag is string => typeof tag === 'string')
.map((tag: string) => tag.trim());
}
// Validate content updates

View file

@ -55,12 +55,13 @@ export async function generateImageWithFlux(options: ImageGenerationOptions): Pr
console.log('Flux Raw Output Type:', typeof output);
console.log('Flux Raw Output:', output);
// Verarbeite das Output
// Verarbeite das Output - Type als unknown, da Replicate verschiedene Formate zurückgibt
let imageUrl: string = '';
const result = output as unknown;
// Wenn es ein Array mit ReadableStreams ist
if (Array.isArray(output) && output.length > 0) {
const firstItem = output[0];
if (Array.isArray(result) && result.length > 0) {
const firstItem = result[0];
// Wenn es ein ReadableStream ist, konvertiere zu Base64
if (
@ -124,8 +125,8 @@ export async function generateImageWithFlux(options: ImageGenerationOptions): Pr
}
}
// Wenn es direkt ein String ist
else if (typeof output === 'string' && output.startsWith('http')) {
imageUrl = output;
else if (typeof result === 'string' && result.startsWith('http')) {
imageUrl = result;
}
if (!imageUrl) {

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

@ -3,20 +3,32 @@
import { fade, fly, scale } from 'svelte/transition';
import { cubicOut, elasticOut } from 'svelte/easing';
// Props - optional message for simple loading states
interface Props {
message?: string;
}
let { message }: Props = $props();
// Reactive state from store
let loading = $derived($loadingStore);
let minimized = $state(false);
let toastMode = $state(false);
// Use store title or fallback to message prop
let displayTitle = $derived(loading.title || message || 'Laden...');
// Calculate overall progress
let progress = $derived(() => {
if (!loading.steps.length) return 0;
const completed = loading.steps.filter((s) => s.status === 'completed').length;
return (completed / loading.steps.length) * 100;
});
// Show overlay if store is loading OR if message prop is provided
let showOverlay = $derived(loading.isLoading || !!message);
</script>
{#if loading.isLoading}
{#if showOverlay}
<!-- Overlay -->
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-theme-overlay backdrop-blur-sm"
@ -45,7 +57,7 @@
<div class="relative flex items-center justify-between">
<h2 class="text-xl font-semibold text-theme-text-primary">
{loading.title}
{displayTitle}
</h2>
<!-- Minimize button -->

View file

@ -1,6 +1,6 @@
import type { ContentNode } from '$lib/types/content';
interface ReferenceData {
export interface ReferenceData {
slug: string;
title: string;
kind: 'character' | 'place' | 'object';

View file

@ -0,0 +1,186 @@
/**
* Auth Store - Manages authentication state using Svelte 5 runes
* Using Mana Core Auth
*/
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
// Initialize Mana Core Auth only on the client side
const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType<typeof initializeWebAuth>['authService'] | null = null;
let _tokenManager: ReturnType<typeof initializeWebAuth>['tokenManager'] | null = null;
function getAuthService() {
if (!browser) return null;
if (!_authService) {
const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
return _authService;
}
// State
let user = $state<UserData | null>(null);
let loading = $state(true);
let initialized = $state(false);
export const authStore = {
// Getters
get user() {
return user;
},
get loading() {
return loading;
},
get isAuthenticated() {
return !!user;
},
get initialized() {
return initialized;
},
/**
* Initialize auth state from stored tokens
*/
async initialize() {
if (initialized) return;
const authService = getAuthService();
if (!authService) {
initialized = true;
loading = false;
return;
}
loading = true;
try {
const authenticated = await authService.isAuthenticated();
if (authenticated) {
const userData = await authService.getUserFromToken();
user = userData;
}
initialized = true;
} catch (error) {
console.error('Failed to initialize auth:', error);
user = null;
} finally {
loading = false;
}
},
/**
* Sign in with email and password
*/
async signIn(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.signIn(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Login failed' };
}
// Get user data from token
const userData = await authService.getUserFromToken();
user = userData;
return { success: true, error: null };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Sign up with email and password
*/
async signUp(email: string, password: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server', needsVerification: false };
}
try {
const result = await authService.signUp(email, password);
if (!result.success) {
return { success: false, error: result.error || 'Signup failed', needsVerification: false };
}
// Mana Core Auth requires separate login after signup
if (result.needsVerification) {
return { success: true, error: null, needsVerification: true };
}
// Auto sign in after successful signup
const signInResult = await this.signIn(email, password);
return { ...signInResult, needsVerification: false };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage, needsVerification: false };
}
},
/**
* Sign out
*/
async signOut() {
const authService = getAuthService();
if (!authService) {
user = null;
return;
}
try {
await authService.signOut();
user = null;
} catch (error) {
console.error('Sign out error:', error);
// Clear user even if sign out fails
user = null;
}
},
/**
* Send password reset email
*/
async resetPassword(email: string) {
const authService = getAuthService();
if (!authService) {
return { success: false, error: 'Auth not available on server' };
}
try {
const result = await authService.forgotPassword(email);
if (!result.success) {
return { success: false, error: result.error || 'Password reset failed' };
}
return { success: true, error: null };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return { success: false, error: errorMessage };
}
},
/**
* Get access token for API calls
*/
async getAccessToken() {
const authService = getAuthService();
if (!authService) {
return null;
}
return await authService.getAppToken();
},
};

View file

@ -51,7 +51,7 @@ function createLoadingStore() {
currentStep: -1,
});
let funFactInterval: NodeJS.Timeout | null = null;
let funFactInterval: ReturnType<typeof setInterval> | null = null;
return {
subscribe,
@ -95,14 +95,14 @@ function createLoadingStore() {
// Complete current step
if (state.currentStep >= 0 && state.currentStep < state.steps.length) {
state.steps[state.currentStep].status = 'completed';
const currentStep = state.steps[state.currentStep];
currentStep.status = 'completed';
if (message) {
state.steps[state.currentStep].message = message;
currentStep.message = message;
}
// Calculate duration for completed step
if (state.steps[state.currentStep].startTime) {
state.steps[state.currentStep].duration =
now - state.steps[state.currentStep].startTime;
if (currentStep.startTime !== undefined) {
currentStep.duration = now - currentStep.startTime;
}
}

View file

@ -85,8 +85,8 @@ function createThemeStore() {
},
toggleMode: () => {
update((current) => {
const newMode = current.mode === 'light' ? 'dark' : 'light';
const newState = { ...current, mode: newMode };
const newMode: ThemeMode = current.mode === 'light' ? 'dark' : 'light';
const newState: ThemeState = { ...current, mode: newMode };
applyTheme(newState);
return newState;
});

View file

@ -351,11 +351,11 @@ export function generateCssVariables(
// Flatten the theme colors into CSS variables
Object.entries(colors).forEach(([category, values]) => {
if (typeof values === 'object') {
Object.entries(values).forEach(([key, value]) => {
if (typeof values === 'object' && values !== null) {
Object.entries(values as Record<string, string>).forEach(([key, value]) => {
variables[getCssVariableName(`${category}.${key}`)] = value;
});
} else {
} else if (typeof values === 'string') {
variables[getCssVariableName(category)] = values;
}
});

View file

@ -64,6 +64,8 @@ export interface ContentData {
_links?: Record<string, string[]>;
_aliases?: string[];
_i18n?: Record<string, any>;
// Index signature für dynamische Content-Felder
[key: string]: string | Record<string, string[]> | string[] | Record<string, any> | undefined;
}
export interface StoryEntry {

View file

@ -11,7 +11,6 @@ marked.setOptions({
breaks: true, // Convert \n to <br>
gfm: true, // GitHub Flavored Markdown
pedantic: false,
sanitize: false, // We'll trust our own content
});
/**
@ -76,7 +75,7 @@ export async function renderMarkdownSmart(
});
// 4. Render markdown
let html = marked(protectedText);
let html = String(marked.parse(protectedText));
// 5. Restore references with smart display
placeholders.forEach((ref, index) => {
@ -121,7 +120,7 @@ export function renderMarkdown(text: string): string {
});
// Render markdown
let html = marked(protectedText);
let html = String(marked.parse(protectedText));
// Restore @references as links with formatted names
references.forEach((ref, index) => {

View file

@ -1,6 +1,5 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { supabase } from '$lib/supabaseClient';
import type { CustomFieldData, CustomFieldSchema } from '$lib/types/customFields';
// GET /api/nodes/[slug]/custom-data - Get custom field data for a node
@ -14,7 +13,7 @@ export const GET: RequestHandler = async ({ params, locals }) => {
try {
// Get the node with its custom data
const { data: node, error: fetchError } = await supabase
const { data: node, error: fetchError } = await locals.supabase
.from('content_nodes')
.select('id, slug, custom_data, custom_schema, owner_id, visibility')
.eq('slug', slug)
@ -64,7 +63,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
const customData = body.data as CustomFieldData;
// Get the node to check ownership and schema
const { data: node, error: fetchError } = await supabase
const { data: node, error: fetchError } = await locals.supabase
.from('content_nodes')
.select('id, owner_id, custom_schema')
.eq('slug', slug)
@ -88,7 +87,7 @@ export const PUT: RequestHandler = async ({ params, request, locals }) => {
}
// Update the custom data
const { error: updateError } = await supabase
const { error: updateError } = await locals.supabase
.from('content_nodes')
.update({
custom_data: customData,
@ -132,7 +131,7 @@ export const PATCH: RequestHandler = async ({ params, request, locals }) => {
const updates = body.data as Partial<CustomFieldData>;
// Get the current node data
const { data: node, error: fetchError } = await supabase
const { data: node, error: fetchError } = await locals.supabase
.from('content_nodes')
.select('id, owner_id, custom_schema, custom_data')
.eq('slug', slug)
@ -162,7 +161,7 @@ export const PATCH: RequestHandler = async ({ params, request, locals }) => {
}
// Update the custom data
const { error: updateError } = await supabase
const { error: updateError } = await locals.supabase
.from('content_nodes')
.update({
custom_data: mergedData,

View file

@ -1,8 +1,6 @@
<script lang="ts">
import { createClient } from '$lib/supabase/client';
import { goto } from '$app/navigation';
const supabase = createClient();
import { authStore } from '$lib/stores/authStore.svelte';
let email = $state('');
let password = $state('');
@ -16,17 +14,21 @@
try {
if (mode === 'login') {
const { error: authError } = await supabase.auth.signInWithPassword({
email,
password,
});
if (authError) throw authError;
const result = await authStore.signIn(email, password);
if (!result.success) {
error = result.error || 'Anmeldung fehlgeschlagen';
return;
}
} else {
const { error: authError } = await supabase.auth.signUp({
email,
password,
});
if (authError) throw authError;
const result = await authStore.signUp(email, password);
if (!result.success) {
error = result.error || 'Registrierung fehlgeschlagen';
return;
}
if (result.needsVerification) {
error = 'Bitte bestätige deine E-Mail-Adresse';
return;
}
}
goto('/');

View file

@ -0,0 +1,10 @@
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
/**
* Logout endpoint - redirects to login page
* Actual logout is handled client-side by authStore.signOut()
*/
export const POST: RequestHandler = async () => {
redirect(303, '/auth/login');
};

Some files were not shown because too many files have changed in this diff Show more