import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, h as StudyContentSource, C as CourseDBInterface } from './contentSource-BP9hznNV.js';
export { J as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, G as ClassroomRegistrationDoc, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, K as CourseRegistration, b as CoursesDBInterface, V as DocumentUpdater, O as NavigatorRole, P as NavigatorRoles, N as Navigators, j as ScheduledCard, H as SessionTrackingData, L as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, I as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, M as getCardOrigin, r as getStudySource, R as isFilter, Q as isGenerator, p as isReview, X as newInterval } from './contentSource-BP9hznNV.js';
import { D as DataLayerProvider } from './dataLayerProvider-MDTxXq2l.js';
import { C as CardHistory, c as CardRecord } from './types-legacy-DDY4N-Uq.js';
export { d as CardData, e as CourseListData, g as DataShapeData, f as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, h as QuestionData, i as QuestionRecord, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-DDY4N-Uq.js';
import { Loggable } from './core/index.js';
export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, CardGenerator, CardGeneratorFactory, FilterContext, GeneratorContext, ImportResult, StrategyStateDoc, StrategyStateId, areQuestionRecords, buildStrategyStateId, docIsDeleted, getCardHistoryID, importParsedCards, isQuestionRecord, parseCardHistoryID, validateProcessorConfig } from './core/index.js';
import { TagFilter, DataShape, CourseConfig } from '@vue-skuilder/common';
import { S as StaticCourseManifest } from './types-DQaXnuoc.js';
export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-DQaXnuoc.js';
import { F as FileSystemAdapter } from './index-Dj0SEgk3.js';
export { C as CouchDBToStaticPacker, a as FileStats, b as FileSystemError } from './index-Dj0SEgk3.js';
import 'moment';

/**
 * Service responsible for ELO rating calculations and updates.
 */
declare class EloService {
    private dataLayer;
    private user;
    constructor(dataLayer: DataLayerProvider, user: UserDBInterface);
    /**
     * Updates both user and card ELO ratings based on user performance.
     * @param userScore Score between 0-1 representing user performance
     * @param course_id Course identifier
     * @param card_id Card identifier
     * @param userCourseRegDoc User's course registration document (will be mutated)
     * @param currentCard Current card session record
     * @param k Optional K-factor for ELO calculation
     */
    updateUserAndCardElo(userScore: number, course_id: string, card_id: string, userCourseRegDoc: CourseRegistrationDoc, currentCard: StudySessionRecord, k?: number): Promise<void>;
}

/**
 * Service responsible for Spaced Repetition System (SRS) scheduling logic.
 */
declare class SrsService {
    private user;
    constructor(user: UserDBInterface);
    /**
     * Calculates the next review time for a card based on its history and
     * schedules it in the user's database.
     * @param history The full history of the card.
     * @param item The study session item, used to determine if a previous review needs to be cleared.
     */
    scheduleReview(history: CardHistory<CardRecord>, item: StudySessionItem): Promise<void>;
}

/**
 * Service responsible for orchestrating the complete response processing workflow.
 * Coordinates SRS scheduling and ELO updates for user card interactions.
 */
declare class ResponseProcessor {
    private srsService;
    private eloService;
    constructor(srsService: SrsService, eloService: EloService);
    /**
     * Processes a user's response to a card, handling SRS scheduling and ELO updates.
     * @param cardRecord User's response record
     * @param cardHistory Promise resolving to the card's history
     * @param studySessionItem Current study session item
     * @param courseRegistrationDoc User's course registration (for ELO updates)
     * @param currentCard Current study session record
     * @param courseId Course identifier
     * @param cardId Card identifier
     * @param maxAttemptsPerView Maximum attempts allowed per view
     * @param maxSessionViews Maximum session views for this card
     * @param sessionViews Current number of session views
     * @returns ResponseResult with navigation and UI instructions
     */
    processResponse(cardRecord: CardRecord, cardHistory: Promise<CardHistory<CardRecord>>, studySessionItem: StudySessionItem, courseRegistrationDoc: CourseRegistrationDoc, currentCard: StudySessionRecord, courseId: string, cardId: string, maxAttemptsPerView: number, maxSessionViews: number, sessionViews: number): Promise<ResponseResult>;
    /**
     * Handles processing for correct responses: SRS scheduling and ELO updates.
     */
    private processCorrectResponse;
    /**
     * Handles processing for incorrect responses: ELO updates only.
     */
    private processIncorrectResponse;
}

interface HydratedCard<TView = unknown> {
    item: StudySessionItem;
    view: TView;
    data: any[];
}

interface MigrationOptions {
    chunkBatchSize: number;
    validateRoundTrip: boolean;
    cleanupOnFailure: boolean;
    timeout: number;
}
interface MigrationResult {
    success: boolean;
    documentsRestored: number;
    attachmentsRestored: number;
    designDocsRestored: number;
    courseConfigRestored: number;
    errors: string[];
    warnings: string[];
    migrationTime: number;
    tempDatabaseName?: string;
}
interface ValidationResult {
    valid: boolean;
    documentCountMatch: boolean;
    attachmentIntegrity: boolean;
    viewFunctionality: boolean;
    issues: ValidationIssue[];
}
interface ValidationIssue {
    type: 'error' | 'warning';
    category: 'documents' | 'attachments' | 'views' | 'metadata' | 'course_config';
    message: string;
    details?: any;
}
interface DocumentCounts {
    [docType: string]: number;
}
interface RestoreProgress {
    phase: 'manifest' | 'design_docs' | 'course_config' | 'documents' | 'attachments' | 'validation';
    current: number;
    total: number;
    message: string;
}
interface StaticCourseValidation {
    valid: boolean;
    manifestExists: boolean;
    chunksExist: boolean;
    attachmentsExist: boolean;
    errors: string[];
    warnings: string[];
    courseId?: string;
    courseName?: string;
}
interface AggregatedDocument {
    _id: string;
    _attachments?: Record<string, any>;
    docType: string;
    [key: string]: any;
}
interface AttachmentUploadResult {
    success: boolean;
    attachmentName: string;
    docId: string;
    error?: string;
}
declare const DEFAULT_MIGRATION_OPTIONS: MigrationOptions;

declare class StaticToCouchDBMigrator {
    private options;
    private progressCallback?;
    private fs?;
    constructor(options?: Partial<MigrationOptions>, fileSystemAdapter?: FileSystemAdapter);
    /**
     * Set a progress callback to receive updates during migration
     */
    setProgressCallback(callback: (progress: RestoreProgress) => void): void;
    /**
     * Migrate a static course to CouchDB
     */
    migrateCourse(staticPath: string, targetDB: PouchDB.Database): Promise<MigrationResult>;
    /**
     * Load and parse the manifest file
     */
    private loadManifest;
    /**
     * Restore design documents to CouchDB
     */
    private restoreDesignDocuments;
    /**
     * Aggregate documents from all chunks
     */
    private aggregateDocuments;
    /**
     * Load documents from a single chunk file
     */
    private loadChunk;
    /**
     * Upload documents to CouchDB in batches
     */
    private uploadDocuments;
    /**
     * Upload attachments from filesystem to CouchDB
     */
    private uploadAttachments;
    /**
     * Upload a single attachment file
     */
    private uploadSingleAttachment;
    /**
     * Restore CourseConfig document from manifest
     */
    private restoreCourseConfig;
    /**
     * Calculate expected document counts from manifest
     */
    private calculateExpectedCounts;
    /**
     * Clean up database after failed migration
     */
    private cleanupFailedMigration;
    /**
     * Report progress to callback if available
     */
    private reportProgress;
    /**
     * Check if a path is a local file path (vs URL)
     */
    private isLocalPath;
}

/**
 * Validate that a static course directory contains all required files
 */
declare function validateStaticCourse(staticPath: string, fs?: FileSystemAdapter): Promise<StaticCourseValidation>;
/**
 * Validate the result of a migration by checking document counts and integrity
 */
declare function validateMigration(targetDB: PouchDB.Database, expectedCounts: DocumentCounts, manifest: StaticCourseManifest): Promise<ValidationResult>;

/**
 * Get the application data directory for the current platform
 * Uses ~/.tuilder as requested by user for simplicity
 */
declare function getAppDataDirectory(): string;
/**
 * Ensure the application data directory exists
 * Creates directory recursively if it doesn't exist
 */
declare function ensureAppDataDirectory(): Promise<string>;
/**
 * Get the full path for a PouchDB database file
 * @param dbName - The database name (e.g., 'userdb-Colin')
 */
declare function getDbPath(dbName: string): string;
/**
 * Initialize data directory for PouchDB usage
 * Should be called once at application startup
 */
declare function initializeDataDirectory(): Promise<void>;

/**
 * Represents a batch of content fetched from a single StudyContentSource.
 */
interface SourceBatch {
    sourceIndex: number;
    weighted: WeightedCard[];
}
/**
 * Strategy interface for mixing content from multiple sources into a unified
 * set of weighted candidates.
 *
 * Different implementations can provide different balancing strategies:
 * - QuotaRoundRobinMixer: Equal representation per source
 * - MinMaxNormalizingMixer: Score normalization then global sort
 * - PercentileBucketMixer: Bucketed round-robin
 * etc.
 */
interface SourceMixer {
    /**
     * Mix weighted cards from multiple sources into a unified, ordered list.
     *
     * @param batches - Content batches from each source
     * @param limit - Target number of cards to return
     * @returns Mixed and ordered weighted cards
     */
    mix(batches: SourceBatch[], limit: number): WeightedCard[];
}
/**
 * Simple quota-based mixer: allocates equal representation to each source,
 * taking the top-N cards by score from each.
 *
 * Guarantees balanced representation across sources regardless of absolute
 * score differences. A low-scoring source gets the same quota as a high-scoring
 * source.
 *
 * This is the KISS approach - simple, predictable, and fair in terms of
 * source representation (though not necessarily optimal in terms of absolute
 * card quality).
 */
declare class QuotaRoundRobinMixer implements SourceMixer {
    mix(batches: SourceBatch[], limit: number): WeightedCard[];
}

interface StudySessionRecord {
    card: {
        course_id: string;
        card_id: string;
        card_elo: number;
    };
    item: StudySessionItem;
    records: CardRecord[];
}

type SessionAction = 'dismiss-success' | 'dismiss-failed' | 'marked-failed' | 'dismiss-error';
interface ResponseResult {
    nextCardAction: Exclude<SessionAction, 'dismiss-error'> | 'none';
    shouldLoadNextCard: boolean;
    isCorrect: boolean;
    performanceScore?: number;
    shouldClearFeedbackShadow: boolean;
}
interface SessionServices {
    response: ResponseProcessor;
}
declare class SessionController<TView = unknown> extends Loggable {
    _className: string;
    services: SessionServices;
    private srsService;
    private eloService;
    private hydrationService;
    private mixer;
    private sources;
    private _sessionRecord;
    set sessionRecord(r: StudySessionRecord[]);
    private _currentCard;
    private reviewQ;
    private newQ;
    private failedQ;
    private startTime;
    private endTime;
    private _secondsRemaining;
    get secondsRemaining(): number;
    get report(): string;
    get detailedReport(): string;
    private _intervalHandle;
    /**
     * @param sources - Array of content sources to mix for the session
     * @param time - Session duration in seconds
     * @param dataLayer - Data layer provider
     * @param getViewComponent - Function to resolve view components
     * @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
     */
    constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer);
    private tick;
    /**
     * Returns a rough, erring toward conservative, guess at
     * the amount of time the failed cards queue will require
     * to clean up.
     *
     * (seconds)
     */
    private estimateCleanupTime;
    /**
     * Extremely rough, conservative, estimate of amound of time to complete
     * all scheduled reviews
     */
    private estimateReviewTime;
    prepareSession(): Promise<void>;
    addTime(seconds: number): void;
    get failedCount(): number;
    toString(): string;
    reportString(): string;
    /**
     * Returns debug information about the current session state.
     * Used by SessionControllerDebug component for runtime inspection.
     */
    getDebugInfo(): {
        api: {
            mode: string;
            description: string;
        };
        reviewQueue: {
            length: number;
            dequeueCount: number;
            items: {
                courseID: any;
                cardID: any;
                status: any;
            }[];
        };
        newQueue: {
            length: number;
            dequeueCount: number;
            items: {
                courseID: any;
                cardID: any;
                status: any;
            }[];
        };
        failedQueue: {
            length: number;
            dequeueCount: number;
            items: {
                courseID: any;
                cardID: any;
                status: any;
            }[];
        };
        hydratedCache: {
            count: number;
            cardIds: string[];
        };
    };
    /**
     * Fetch content using the getWeightedCards API and mix across sources.
     *
     * This method:
     * 1. Fetches weighted cards from each source
     * 2. Fetches full review data (we need ScheduledCard fields for queue)
     * 3. Uses SourceMixer to balance content across sources
     * 4. Populates review and new card queues with mixed results
     */
    private getWeightedContent;
    /**
     * Returns items that should be pre-hydrated.
     * Deterministic: top N items from each queue to ensure coverage.
     * Failed queue items will typically already be hydrated (from initial render).
     */
    private _getItemsToHydrate;
    /**
     * Selects the next item to present to the user.
     * Nondeterministic: uses probability to balance between queues based on session state.
     */
    private _selectNextItemToHydrate;
    nextCard(action?: SessionAction): Promise<HydratedCard<TView> | null>;
    /**
     * Public API for processing user responses to cards.
     * @param cardRecord User's response record
     * @param cardHistory Promise resolving to the card's history
     * @param courseRegistrationDoc User's course registration document
     * @param currentCard Current study session record
     * @param courseId Course identifier
     * @param cardId Card identifier
     * @param maxAttemptsPerView Maximum attempts allowed per view
     * @param maxSessionViews Maximum session views for this card
     * @param sessionViews Current number of session views
     * @returns ResponseResult with navigation and UI instructions
     */
    submitResponse(cardRecord: CardRecord, cardHistory: Promise<CardHistory<CardRecord>>, courseRegistrationDoc: CourseRegistrationDoc, currentCard: StudySessionRecord, courseId: string, cardId: string, maxAttemptsPerView: number, maxSessionViews: number, sessionViews: number): Promise<ResponseResult>;
    private dismissCurrentCard;
    /**
     * Remove an item from its source queue after consumption by nextCard().
     */
    private removeItemFromQueue;
}

/**
 * A StudyContentSource that filters cards based on tag inclusion/exclusion.
 *
 * This enables ephemeral, tag-scoped study sessions where users can focus
 * on specific topics within a course without permanent configuration.
 *
 * Filter logic:
 * - If `include` is non-empty: card must have at least one of the included tags
 * - If `exclude` is non-empty: card must not have any of the excluded tags
 * - Both filters are applied (include first, then exclude)
 */
declare class TagFilteredContentSource implements StudyContentSource {
    private courseId;
    private filter;
    private user;
    private resolvedCardIds;
    constructor(courseId: string, filter: TagFilter, user: UserDBInterface);
    /**
     * Resolves the TagFilter to a set of eligible card IDs.
     *
     * - Cards in `include` tags are OR'd together (card needs at least one)
     * - Cards in `exclude` tags are removed from the result
     */
    private resolveFilteredCardIds;
    /**
     * Get cards with suitability scores for presentation.
     *
     * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.
     * TagFilteredContentSource does not currently support pluggable navigation
     * strategies - it returns flat-scored candidates.
     *
     * @param limit - Maximum number of cards to return
     * @returns Cards sorted by score descending (all scores = 1.0)
     */
    getWeightedCards(limit: number): Promise<WeightedCard[]>;
    /**
     * Clears the cached resolved card IDs.
     * Call this if the underlying tag data may have changed during a session.
     */
    clearCache(): void;
    /**
     * Returns the course ID this source is filtering.
     */
    getCourseId(): string;
    /**
     * Returns the active tag filter.
     */
    getFilter(): TagFilter;
}

interface CourseLookupDoc {
    _id: string;
    _rev: string;
    name: string;
    disambiguator?: string;
}
/**
 * A Lookup table of existant courses. Each docID in this DB correspondes to a
 * course database whose name is `coursedb-{docID}`
 */
declare class CourseLookup {
    private static _dbInstance;
    /**
     * Static getter for the PouchDB database instance.
     * Connects using ENV variables and caches the instance.
     * Throws an error if required ENV variables are not set.
     */
    private static get _db();
    /**
     * Adds a new course to the lookup database, and returns the courseID
     * @param courseName
     * @returns
     */
    static add(courseName: string): Promise<string>;
    /**
     * Adds a new course to the lookup database with a specific courseID
     * @param courseId The specific course ID to use
     * @param courseName The course name
     * @param disambiguator Optional disambiguator
     * @returns Promise<void>
     */
    static addWithId(courseId: string, courseName: string, disambiguator?: string): Promise<void>;
    /**
     * Removes a course from the index
     * @param courseID
     */
    static delete(courseID: string): Promise<PouchDB.Core.Response>;
    static allCourseWare(): Promise<CourseLookupDoc[]>;
    static updateDisambiguator(courseID: string, disambiguator?: string): Promise<PouchDB.Core.Response>;
    static isCourse(courseID: string): Promise<boolean>;
}

/**
 * Interface for custom questions data structure returned by allCustomQuestions()
 */
interface CustomQuestionsData {
    courses: {
        name: string;
    }[];
    questionClasses: {
        name: string;
        dataShapes?: DataShape[];
        views?: {
            name?: string;
        }[];
        seedData?: unknown[];
    }[];
    dataShapes: DataShape[];
    views: {
        name?: string;
    }[];
    meta: {
        questionCount: number;
        dataShapeCount: number;
        viewCount: number;
        courseCount: number;
        packageName: string;
        sourceDirectory: string;
    };
}
/**
 * Interface for processed question data for registration
 */
interface ProcessedQuestionData {
    name: string;
    course: string;
    questionClass: {
        name: string;
        dataShapes?: DataShape[];
        views?: {
            name?: string;
        }[];
        seedData?: unknown[];
    };
    dataShapes: DataShape[];
    views: {
        name?: string;
    }[];
}
/**
 * Interface for processed data shape for registration
 */
interface ProcessedDataShape {
    name: string;
    course: string;
    dataShape: DataShape;
}
/**
 * Check if a data shape is already registered in the course config with valid schema
 */
declare function isDataShapeRegistered(dataShape: ProcessedDataShape, courseConfig: CourseConfig): boolean;
declare function isDataShapeSchemaAvailable(dataShape: ProcessedDataShape, courseConfig: CourseConfig): boolean;
/**
 * Check if a question type is already registered in the course config
 */
declare function isQuestionTypeRegistered(question: ProcessedQuestionData, courseConfig: CourseConfig): boolean;
/**
 * Process custom questions data into registration-ready format
 */
declare function processCustomQuestionsData(customQuestions: CustomQuestionsData): {
    dataShapes: ProcessedDataShape[];
    questions: ProcessedQuestionData[];
};
/**
 * Register a data shape in the course config
 */
declare function registerDataShape(dataShape: ProcessedDataShape, courseConfig: CourseConfig): boolean;
/**
 * Register a question type in the course config
 */
declare function registerQuestionType(question: ProcessedQuestionData, courseConfig: CourseConfig): boolean;
/**
 * Register seed data for a question type
 *
 * @param question - The processed question data
 * @param courseDB - The course database interface
 * @param username - The username to attribute seed data to
 */
declare function registerSeedData(question: ProcessedQuestionData, courseDB: CourseDBInterface, username: string): Promise<void>;
/**
 * Register BlanksCard (markdown fillIn) question type specifically
 */
declare function registerBlanksCard(BlanksCard: {
    name: string;
    views?: {
        name?: string;
    }[];
}, BlanksCardDataShapes: DataShape[], courseConfig: CourseConfig, courseDB: CourseDBInterface, username?: string): Promise<{
    success: boolean;
    errorMessage?: string;
}>;
/**
 * Main function to register all custom question types and data shapes
 *
 * @param customQuestions - The custom questions data from allCustomQuestions()
 * @param courseConfig - The course configuration object
 * @param courseDB - The course database interface
 * @param username - The username to attribute seed data to
 */
declare function registerCustomQuestionTypes(customQuestions: CustomQuestionsData, courseConfig: CourseConfig, courseDB: CourseDBInterface, username?: string): Promise<{
    success: boolean;
    registeredCount: number;
    errorMessage?: string;
}>;

declare const NOT_SET: "NOT_SET";
interface DBEnv {
    COUCHDB_SERVER_URL: string;
    COUCHDB_SERVER_PROTOCOL: string;
    COUCHDB_USERNAME?: string;
    COUCHDB_PASSWORD?: string;
    LOCAL_STORAGE_PREFIX: string;
}
declare const ENV: DBEnv;

interface DataLayerConfig {
    type: 'couch' | 'static';
    options: {
        staticContentPath?: string;
        localStoragePrefix?: string;
        manifests?: Record<string, StaticCourseManifest>;
        COUCHDB_SERVER_URL?: string;
        COUCHDB_SERVER_PROTOCOL?: string;
        COUCHDB_USERNAME?: string;
        COUCHDB_PASSWORD?: string;
        COURSE_IDS?: string[];
    };
}
/**
 * Initialize the data layer with the specified configuration
 */
declare function initializeDataLayer(config: DataLayerConfig): Promise<DataLayerProvider>;
/**
 * Get the initialized data layer instance
 * @throws Error if not initialized
 */
declare function getDataLayer(): DataLayerProvider;
/**
 * Reset the data layer (primarily for testing)
 */
declare function _resetDataLayer(): Promise<void>;

type UserAccountStatus = 'pending_verification' | 'verified' | 'suspended';
interface Entitlement {
    status: 'trial' | 'paid';
    registrationDate: string;
    purchaseDate: string;
    expires?: string;
}
type UserEntitlements = Record<string, Entitlement>;
interface CouchDbUserDoc extends PouchDB.Authentication.User {
    name: string;
    email: string;
    status: UserAccountStatus;
    verificationToken?: string | null;
    verificationTokenExpiresAt?: string | null;
    passwordResetToken?: string | null;
    passwordResetTokenExpiresAt?: string | null;
    entitlements: UserEntitlements;
}

export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, NOT_SET, type ProcessedDataShape, type ProcessedQuestionData, QuotaRoundRobinMixer, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SourceBatch, type SourceMixer, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, processCustomQuestionsData, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, validateMigration, validateStaticCourse };
