```typescript
/**
 * Splits an array into consecutive groups of at most size elements,
 * preserving order; the final group is shorter when the length does not divide evenly.
 * 
 * @param items The input array to be chunked, must not be mutated.
 * @param size A positive integer specifying the maximum number of elements per chunk.
 * @returns An array of chunks, where each chunk is an array of at most size elements.
 */
export function chunk<T>(items: readonly T[], size: number): T[][] {
    // Check if size is a positive integer
    if (size <= 0) {
        throw new Error('Size must be a positive integer');
    }

    // Initialize the result array with an empty chunk
    const result: T[][] = [Array(size)];
    let currentChunkIndex = 0;

    // Iterate over the input array
    for (const item of items) {
        // Find the next available index in the result array
        while (currentChunkIndex >= result.length) {
            result.push(Array(size));
            currentChunkIndex = result.length - 1;
        }

        // Add the item to the chunk at the next available index
        result[currentChunkIndex].push(item);

        // If the chunk is full, move on to the next one
        if (result[currentChunkIndex].length === size) {
            currentChunkIndex++;
        }
    }

    return result;
}
```