@olonjs/cli 3.0.145 → 3.0.146

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1817,7 +1817,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
1817
1817
  "@tiptap/extension-link": "^2.11.5",
1818
1818
  "@tiptap/react": "^2.11.5",
1819
1819
  "@tiptap/starter-kit": "^2.11.5",
1820
- "@olonjs/core": "^1.1.11",
1820
+ "@olonjs/core": "^1.1.12",
1821
1821
  "class-variance-authority": "^0.7.1",
1822
1822
  "clsx": "^2.1.1",
1823
1823
  "lucide-react": "^0.474.0",
@@ -2959,7 +2959,7 @@ import { startCloudSaveStream } from '@olonjs/core';
2959
2959
  import siteData from '@/data/config/site.json';
2960
2960
  import themeData from '@/data/config/theme.json';
2961
2961
  import menuData from '@/data/config/menu.json';
2962
- import libriData from '@/data/collections/libri/libri.json';
2962
+ import { getFileCollections } from '@/lib/getFileCollections';
2963
2963
  import { getFilePages } from '@/lib/getFilePages';
2964
2964
  import { DopaDrawer } from '@/components/save-drawer/DopaDrawer';
2965
2965
  import { EmptyTenantView } from '@/components/empty-tenant';
@@ -2991,9 +2991,7 @@ const TENANT_ID = 'alpha';
2991
2991
 
2992
2992
  const filePages = getFilePages();
2993
2993
  const fileSiteConfig = siteData as unknown as SiteConfig;
2994
- const collections = {
2995
- libri: libriData as unknown as Record<string, unknown>,
2996
- } satisfies NonNullable<JsonPagesConfig['collections']>;
2994
+ const collections = getFileCollections();
2997
2995
  const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
2998
2996
  const ASSET_UPLOAD_MAX_RETRIES = 2;
2999
2997
  const ASSET_UPLOAD_TIMEOUT_MS = 20_000;
@@ -4114,6 +4112,34 @@ export default App;
4114
4112
  END_OF_FILE_CONTENT
4115
4113
  # SKIP: src/App.tsx:Zone.Identifier is binary and cannot be embedded as text.
4116
4114
  mkdir -p "src/collections"
4115
+ mkdir -p "src/collections/autori"
4116
+ echo "Creating src/collections/autori/index.ts..."
4117
+ cat << 'END_OF_FILE_CONTENT' > "src/collections/autori/index.ts"
4118
+ export { AutoreSchema, AutoriCollectionSchema } from './schema';
4119
+ export type { Autore, AutoriCollection } from './types';
4120
+
4121
+ END_OF_FILE_CONTENT
4122
+ echo "Creating src/collections/autori/schema.ts..."
4123
+ cat << 'END_OF_FILE_CONTENT' > "src/collections/autori/schema.ts"
4124
+ import { z } from 'zod';
4125
+ import { BaseCollectionItem } from '@olonjs/core';
4126
+
4127
+ export const AutoreSchema = BaseCollectionItem.extend({
4128
+ name: z.string().describe('ui:text'),
4129
+ });
4130
+
4131
+ export const AutoriCollectionSchema = z.record(z.string(), AutoreSchema);
4132
+
4133
+ END_OF_FILE_CONTENT
4134
+ echo "Creating src/collections/autori/types.ts..."
4135
+ cat << 'END_OF_FILE_CONTENT' > "src/collections/autori/types.ts"
4136
+ import { z } from 'zod';
4137
+ import { AutoreSchema, AutoriCollectionSchema } from './schema';
4138
+
4139
+ export type Autore = z.infer<typeof AutoreSchema>;
4140
+ export type AutoriCollection = z.infer<typeof AutoriCollectionSchema>;
4141
+
4142
+ END_OF_FILE_CONTENT
4117
4143
  mkdir -p "src/collections/libri"
4118
4144
  echo "Creating src/collections/libri/index.ts..."
4119
4145
  cat << 'END_OF_FILE_CONTENT' > "src/collections/libri/index.ts"
@@ -4125,10 +4151,15 @@ echo "Creating src/collections/libri/schema.ts..."
4125
4151
  cat << 'END_OF_FILE_CONTENT' > "src/collections/libri/schema.ts"
4126
4152
  import { z } from 'zod';
4127
4153
  import { BaseCollectionItem } from '@olonjs/core';
4154
+ import { AutoreSchema } from '@/collections/autori';
4155
+
4156
+ const CollectionRefSchema = z.object({
4157
+ $ref: z.string(),
4158
+ });
4128
4159
 
4129
4160
  export const LibroSchema = BaseCollectionItem.extend({
4130
4161
  title: z.string().describe('ui:text'),
4131
- author: z.string().describe('ui:text'),
4162
+ author: z.union([AutoreSchema, CollectionRefSchema]).describe('ui:collection-ref:autori'),
4132
4163
  year: z.number().describe('ui:number'),
4133
4164
  genre: z.string().describe('ui:text'),
4134
4165
  summary: z.string().describe('ui:textarea'),
@@ -4209,6 +4240,106 @@ export function useTheme() {
4209
4240
  return useContext(ThemeContext)
4210
4241
  }
4211
4242
 
4243
+ END_OF_FILE_CONTENT
4244
+ mkdir -p "src/components/authors-list"
4245
+ echo "Creating src/components/authors-list/View.tsx..."
4246
+ cat << 'END_OF_FILE_CONTENT' > "src/components/authors-list/View.tsx"
4247
+ import { useMemo } from 'react';
4248
+ import type { Autore } from '@/collections/autori';
4249
+ import type { AuthorsListData } from './types';
4250
+
4251
+ type AuthorsListViewProps = {
4252
+ data: AuthorsListData;
4253
+ };
4254
+
4255
+ function toAuthors(items: AuthorsListData['items']): Autore[] {
4256
+ return Object.values(items ?? {}).sort((a, b) => a.name.localeCompare(b.name));
4257
+ }
4258
+
4259
+ export function AuthorsListView({ data }: AuthorsListViewProps) {
4260
+ const authors = useMemo(() => toAuthors(data.items), [data.items]);
4261
+
4262
+ return (
4263
+ <main className="min-h-screen bg-background px-6 py-16 text-foreground">
4264
+ <section className="mx-auto flex w-full max-w-5xl flex-col gap-10">
4265
+ <div className="max-w-2xl">
4266
+ {data.eyebrow && (
4267
+ <p
4268
+ data-jp-field="eyebrow"
4269
+ className="text-sm font-medium uppercase tracking-[0.18em] text-muted-foreground"
4270
+ >
4271
+ {data.eyebrow}
4272
+ </p>
4273
+ )}
4274
+ <h1
4275
+ data-jp-field="title"
4276
+ className="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl"
4277
+ >
4278
+ {data.title}
4279
+ </h1>
4280
+ {data.description && (
4281
+ <p
4282
+ data-jp-field="description"
4283
+ className="mt-4 text-base leading-7 text-muted-foreground"
4284
+ >
4285
+ {data.description}
4286
+ </p>
4287
+ )}
4288
+ </div>
4289
+
4290
+ <div data-jp-field="items" className="grid gap-4 sm:grid-cols-2">
4291
+ {authors.map((author) => (
4292
+ <a
4293
+ key={author.id}
4294
+ href={`/authors/${encodeURIComponent(author.id)}/libri`}
4295
+ data-jp-item-id={author.id}
4296
+ data-jp-item-field="items"
4297
+ className="block rounded-xl border border-border bg-card p-5 shadow-sm transition-colors hover:bg-muted/40"
4298
+ >
4299
+ <h2 className="text-xl font-semibold">{author.name}</h2>
4300
+ <p className="mt-2 text-sm text-muted-foreground">
4301
+ Vedi libri di {author.name}
4302
+ </p>
4303
+ </a>
4304
+ ))}
4305
+ </div>
4306
+ </section>
4307
+ </main>
4308
+ );
4309
+ }
4310
+
4311
+ END_OF_FILE_CONTENT
4312
+ echo "Creating src/components/authors-list/index.ts..."
4313
+ cat << 'END_OF_FILE_CONTENT' > "src/components/authors-list/index.ts"
4314
+ export { AuthorsListView } from './View';
4315
+ export { AuthorsListSchema, AuthorsListSettingsSchema } from './schema';
4316
+ export type { AuthorsListData, AuthorsListSettings } from './types';
4317
+
4318
+ END_OF_FILE_CONTENT
4319
+ echo "Creating src/components/authors-list/schema.ts..."
4320
+ cat << 'END_OF_FILE_CONTENT' > "src/components/authors-list/schema.ts"
4321
+ import { z } from 'zod';
4322
+ import { BaseSectionData } from '@olonjs/core';
4323
+ import { AutoreSchema } from '@/collections/autori';
4324
+
4325
+ export const AuthorsListSchema = BaseSectionData.extend({
4326
+ eyebrow: z.string().optional().describe('ui:text'),
4327
+ title: z.string().describe('ui:text'),
4328
+ description: z.string().optional().describe('ui:textarea'),
4329
+ items: z.record(z.string(), AutoreSchema).describe('ui:collection-ref:autori'),
4330
+ });
4331
+
4332
+ export const AuthorsListSettingsSchema = z.object({});
4333
+
4334
+ END_OF_FILE_CONTENT
4335
+ echo "Creating src/components/authors-list/types.ts..."
4336
+ cat << 'END_OF_FILE_CONTENT' > "src/components/authors-list/types.ts"
4337
+ import { z } from 'zod';
4338
+ import { AuthorsListSchema, AuthorsListSettingsSchema } from './schema';
4339
+
4340
+ export type AuthorsListData = z.infer<typeof AuthorsListSchema>;
4341
+ export type AuthorsListSettings = z.infer<typeof AuthorsListSettingsSchema>;
4342
+
4212
4343
  END_OF_FILE_CONTENT
4213
4344
  mkdir -p "src/components/book-detail"
4214
4345
  echo "Creating src/components/book-detail/View.tsx..."
@@ -4219,6 +4350,13 @@ type BookDetailViewProps = {
4219
4350
  data: BookDetailData;
4220
4351
  };
4221
4352
 
4353
+ function getAuthorName(author: BookDetailData['item']['author']): string {
4354
+ if (typeof author === 'object' && author !== null && 'name' in author) {
4355
+ return String(author.name);
4356
+ }
4357
+ return 'Autore';
4358
+ }
4359
+
4222
4360
  export function BookDetailView({ data }: BookDetailViewProps) {
4223
4361
  const book = data.item;
4224
4362
 
@@ -4231,7 +4369,7 @@ export function BookDetailView({ data }: BookDetailViewProps) {
4231
4369
  className="mx-auto w-full max-w-3xl rounded-2xl border border-border bg-card p-8 shadow-sm"
4232
4370
  >
4233
4371
  <a
4234
- href="/libri"
4372
+ href="/"
4235
4373
  className="text-sm font-medium text-muted-foreground hover:text-foreground"
4236
4374
  >
4237
4375
  {data.backLabel}
@@ -4243,7 +4381,7 @@ export function BookDetailView({ data }: BookDetailViewProps) {
4243
4381
  {book.title}
4244
4382
  </h1>
4245
4383
  <p className="mt-4 text-lg text-muted-foreground">
4246
- {book.author}
4384
+ {getAuthorName(book.author)}
4247
4385
  </p>
4248
4386
  <p className="mt-8 text-base leading-8 text-muted-foreground">
4249
4387
  {book.summary}
@@ -4287,7 +4425,8 @@ END_OF_FILE_CONTENT
4287
4425
  mkdir -p "src/components/books-list"
4288
4426
  echo "Creating src/components/books-list/View.tsx..."
4289
4427
  cat << 'END_OF_FILE_CONTENT' > "src/components/books-list/View.tsx"
4290
- import { useMemo, useState } from 'react';
4428
+ import { useEffect, useMemo, useState } from 'react';
4429
+ import { useLocation } from 'react-router-dom';
4291
4430
  import type { Libro } from '@/collections/libri';
4292
4431
  import type { BooksListData } from './types';
4293
4432
 
@@ -4299,14 +4438,50 @@ function toBooks(items: BooksListData['items']): Libro[] {
4299
4438
  return Object.values(items ?? {}).sort((a, b) => a.title.localeCompare(b.title));
4300
4439
  }
4301
4440
 
4441
+ function getAuthorName(author: Libro['author']): string {
4442
+ if (typeof author === 'object' && author !== null && 'name' in author) {
4443
+ return String(author.name);
4444
+ }
4445
+ return 'Autore';
4446
+ }
4447
+
4448
+ function getAuthorId(author: Libro['author']): string | null {
4449
+ if (typeof author === 'object' && author !== null && 'id' in author && typeof author.id === 'string') {
4450
+ return author.id;
4451
+ }
4452
+ if (typeof author === 'object' && author !== null && '$ref' in author && typeof author.$ref === 'string') {
4453
+ const pointer = author.$ref.split('#')[1]?.replace(/^\//, '') ?? '';
4454
+ return pointer.split('/')[0] || null;
4455
+ }
4456
+ return null;
4457
+ }
4458
+
4459
+ function getAuthorFilterFromPath(pathname: string): string | null {
4460
+ const match = pathname.match(/^\/authors\/([^/]+)\/libri\/?$/);
4461
+ return match?.[1] ? decodeURIComponent(match[1]) : null;
4462
+ }
4463
+
4302
4464
  export function BooksListView({ data }: BooksListViewProps) {
4465
+ const location = useLocation();
4466
+ const authorFilter = useMemo(() => {
4467
+ const queryAuthor = new URLSearchParams(location.search).get('author');
4468
+ return queryAuthor || getAuthorFilterFromPath(location.pathname);
4469
+ }, [location.pathname, location.search]);
4303
4470
  const books = useMemo(() => toBooks(data.items), [data.items]);
4471
+ const filteredBooks = useMemo(
4472
+ () => authorFilter ? books.filter((book) => getAuthorId(book.author) === authorFilter) : books,
4473
+ [authorFilter, books]
4474
+ );
4304
4475
  const pageSize = Math.max(1, Math.floor(data.pageSize || 10));
4305
- const totalPages = Math.max(1, Math.ceil(books.length / pageSize));
4476
+ const totalPages = Math.max(1, Math.ceil(filteredBooks.length / pageSize));
4306
4477
  const [page, setPage] = useState(1);
4307
4478
  const currentPage = Math.min(page, totalPages);
4308
4479
  const startIndex = (currentPage - 1) * pageSize;
4309
- const visibleBooks = books.slice(startIndex, startIndex + pageSize);
4480
+ const visibleBooks = filteredBooks.slice(startIndex, startIndex + pageSize);
4481
+
4482
+ useEffect(() => {
4483
+ setPage(1);
4484
+ }, [authorFilter]);
4310
4485
 
4311
4486
  return (
4312
4487
  <main className="min-h-screen bg-background text-foreground px-6 py-16">
@@ -4337,6 +4512,11 @@ export function BooksListView({ data }: BooksListViewProps) {
4337
4512
  </div>
4338
4513
 
4339
4514
  <div data-jp-field="items" className="grid gap-4">
4515
+ {authorFilter && (
4516
+ <p className="text-sm text-muted-foreground">
4517
+ Filtro autore: {authorFilter} · {filteredBooks.length} libri
4518
+ </p>
4519
+ )}
4340
4520
  {visibleBooks.map((book) => (
4341
4521
  <article
4342
4522
  key={book.id}
@@ -4348,7 +4528,7 @@ export function BooksListView({ data }: BooksListViewProps) {
4348
4528
  <div>
4349
4529
  <h2 className="text-xl font-semibold">{book.title}</h2>
4350
4530
  <p className="mt-1 text-sm text-muted-foreground">
4351
- {book.author} · {book.year} · {book.genre}
4531
+ {getAuthorName(book.author)} · {book.year} · {book.genre}
4352
4532
  </p>
4353
4533
  <p className="mt-3 max-w-3xl text-sm leading-6 text-muted-foreground">
4354
4534
  {book.summary}
@@ -4375,7 +4555,7 @@ export function BooksListView({ data }: BooksListViewProps) {
4375
4555
  Precedente
4376
4556
  </button>
4377
4557
  <span className="text-muted-foreground">
4378
- Pagina {currentPage} di {totalPages} · {books.length} libri
4558
+ Pagina {currentPage} di {totalPages} · {filteredBooks.length} libri
4379
4559
  </span>
4380
4560
  <button
4381
4561
  type="button"
@@ -4409,7 +4589,7 @@ export const BooksListSchema = BaseSectionData.extend({
4409
4589
  eyebrow: z.string().optional().describe('ui:text'),
4410
4590
  title: z.string().describe('ui:text'),
4411
4591
  description: z.string().optional().describe('ui:textarea'),
4412
- items: z.record(z.string(), LibroSchema).describe('ui:collection-ref'),
4592
+ items: z.record(z.string(), LibroSchema).describe('ui:collection-ref:libri'),
4413
4593
  pageSize: z.number().default(10).describe('ui:number'),
4414
4594
  });
4415
4595
 
@@ -9007,250 +9187,433 @@ END_OF_FILE_CONTENT
9007
9187
  # SKIP: src/components/ui/tooltip.tsx:Zone.Identifier is binary and cannot be embedded as text.
9008
9188
  mkdir -p "src/data"
9009
9189
  mkdir -p "src/data/collections"
9190
+ mkdir -p "src/data/collections/autori"
9191
+ echo "Creating src/data/collections/autori/autori.json..."
9192
+ cat << 'END_OF_FILE_CONTENT' > "src/data/collections/autori/autori.json"
9193
+ {
9194
+ "george-orwell": {
9195
+ "id": "george-orwell",
9196
+ "name": "George Orwell"
9197
+ },
9198
+ "umberto-eco": {
9199
+ "id": "umberto-eco",
9200
+ "name": "Umberto Eco"
9201
+ },
9202
+ "primo-levi": {
9203
+ "id": "primo-levi",
9204
+ "name": "Primo Levi"
9205
+ },
9206
+ "italo-calvino": {
9207
+ "id": "italo-calvino",
9208
+ "name": "Italo Calvino"
9209
+ },
9210
+ "giuseppe-tomasi-di-lampedusa": {
9211
+ "id": "giuseppe-tomasi-di-lampedusa",
9212
+ "name": "Giuseppe Tomasi di Lampedusa"
9213
+ },
9214
+ "italo-svevo": {
9215
+ "id": "italo-svevo",
9216
+ "name": "Italo Svevo"
9217
+ },
9218
+ "alessandro-manzoni": {
9219
+ "id": "alessandro-manzoni",
9220
+ "name": "Alessandro Manzoni"
9221
+ },
9222
+ "roberto-saviano": {
9223
+ "id": "roberto-saviano",
9224
+ "name": "Roberto Saviano"
9225
+ },
9226
+ "alessandro-baricco": {
9227
+ "id": "alessandro-baricco",
9228
+ "name": "Alessandro Baricco"
9229
+ },
9230
+ "natalia-ginzburg": {
9231
+ "id": "natalia-ginzburg",
9232
+ "name": "Natalia Ginzburg"
9233
+ },
9234
+ "gabriel-garcia-marquez": {
9235
+ "id": "gabriel-garcia-marquez",
9236
+ "name": "Gabriel Garcia Marquez"
9237
+ },
9238
+ "ray-bradbury": {
9239
+ "id": "ray-bradbury",
9240
+ "name": "Ray Bradbury"
9241
+ },
9242
+ "frank-herbert": {
9243
+ "id": "frank-herbert",
9244
+ "name": "Frank Herbert"
9245
+ },
9246
+ "william-gibson": {
9247
+ "id": "william-gibson",
9248
+ "name": "William Gibson"
9249
+ },
9250
+ "j-r-r-tolkien": {
9251
+ "id": "j-r-r-tolkien",
9252
+ "name": "J. R. R. Tolkien"
9253
+ },
9254
+ "j-k-rowling": {
9255
+ "id": "j-k-rowling",
9256
+ "name": "J. K. Rowling"
9257
+ },
9258
+ "jane-austen": {
9259
+ "id": "jane-austen",
9260
+ "name": "Jane Austen"
9261
+ },
9262
+ "herman-melville": {
9263
+ "id": "herman-melville",
9264
+ "name": "Herman Melville"
9265
+ },
9266
+ "fedor-dostoevskij": {
9267
+ "id": "fedor-dostoevskij",
9268
+ "name": "Fedor Dostoevskij"
9269
+ },
9270
+ "lev-tolstoj": {
9271
+ "id": "lev-tolstoj",
9272
+ "name": "Lev Tolstoj"
9273
+ },
9274
+ "michail-bulgakov": {
9275
+ "id": "michail-bulgakov",
9276
+ "name": "Michail Bulgakov"
9277
+ },
9278
+ "cormac-mccarthy": {
9279
+ "id": "cormac-mccarthy",
9280
+ "name": "Cormac McCarthy"
9281
+ },
9282
+ "david-mitchell": {
9283
+ "id": "david-mitchell",
9284
+ "name": "David Mitchell"
9285
+ },
9286
+ "haruki-murakami": {
9287
+ "id": "haruki-murakami",
9288
+ "name": "Haruki Murakami"
9289
+ },
9290
+ "chimamanda-ngozi-adichie": {
9291
+ "id": "chimamanda-ngozi-adichie",
9292
+ "name": "Chimamanda Ngozi Adichie"
9293
+ },
9294
+ "isabel-allende": {
9295
+ "id": "isabel-allende",
9296
+ "name": "Isabel Allende"
9297
+ },
9298
+ "toni-morrison": {
9299
+ "id": "toni-morrison",
9300
+ "name": "Toni Morrison"
9301
+ },
9302
+ "junot-diaz": {
9303
+ "id": "junot-diaz",
9304
+ "name": "Junot Diaz"
9305
+ },
9306
+ "jonathan-franzen": {
9307
+ "id": "jonathan-franzen",
9308
+ "name": "Jonathan Franzen"
9309
+ }
9310
+ }
9311
+
9312
+ END_OF_FILE_CONTENT
9010
9313
  mkdir -p "src/data/collections/libri"
9011
9314
  echo "Creating src/data/collections/libri/libri.json..."
9012
9315
  cat << 'END_OF_FILE_CONTENT' > "src/data/collections/libri/libri.json"
9013
- {
9014
- "1984": {
9015
- "id": "1984",
9016
- "title": "1984",
9017
- "author": "Guido Seiero",
9018
- "year": 1949,
9019
- "genre": "Distopia",
9020
- "summary": "Un regime totalitario controlla linguaggio, memoria e pensiero."
9021
- },
9022
- "il-nome-della-rosa": {
9023
- "id": "il-nome-della-rosa",
9024
- "title": "Il nome della rosa",
9025
- "author": "Umberto Eco",
9026
- "year": 1980,
9027
- "genre": "Romanzo storico",
9028
- "summary": "Un'indagine in un'abbazia medievale diventa una riflessione su conoscenza, potere e interpretazione."
9029
- },
9030
- "se-questo-e-un-uomo": {
9031
- "id": "se-questo-e-un-uomo",
9032
- "title": "Se questo e un uomo",
9033
- "author": "Primo Levi",
9034
- "year": 1947,
9035
- "genre": "Memoria",
9036
- "summary": "La testimonianza essenziale di Levi sull'esperienza del lager e sulla dignita umana."
9037
- },
9038
- "le-citta-invisibili": {
9039
- "id": "le-citta-invisibili",
9040
- "title": "Le citta invisibili",
9041
- "author": "Italo Calvino",
9042
- "year": 1972,
9043
- "genre": "Letteratura fantastica",
9044
- "summary": "Marco Polo racconta a Kublai Khan citta immaginarie che parlano di memoria, desiderio e linguaggio."
9045
- },
9046
- "il-gattopardo": {
9047
- "id": "il-gattopardo",
9048
- "title": "Il Gattopardo",
9049
- "author": "Giuseppe Tomasi di Lampedusa",
9050
- "year": 1958,
9051
- "genre": "Romanzo storico",
9052
- "summary": "Il tramonto dell'aristocrazia siciliana durante l'unificazione italiana."
9053
- },
9054
- "la-coscienza-di-zeno": {
9055
- "id": "la-coscienza-di-zeno",
9056
- "title": "La coscienza di Zeno",
9057
- "author": "Italo Svevo",
9058
- "year": 1923,
9059
- "genre": "Romanzo psicologico",
9060
- "summary": "Un diario ironico e nevrotico attraversa memoria, terapia e autoinganno."
9061
- },
9062
- "i-promessi-sposi": {
9063
- "id": "i-promessi-sposi",
9064
- "title": "I promessi sposi",
9065
- "author": "Alessandro Manzoni",
9066
- "year": 1842,
9067
- "genre": "Classico",
9068
- "summary": "La vicenda di Renzo e Lucia dentro carestia, guerra, peste e provvidenza."
9069
- },
9070
- "il-barone-rampante": {
9071
- "id": "il-barone-rampante",
9072
- "title": "Il barone rampante",
9073
- "author": "Italo Calvino",
9074
- "year": 1957,
9075
- "genre": "Romanzo filosofico",
9076
- "summary": "Cosimo sceglie di vivere sugli alberi e trasforma la distanza in una forma di liberta."
9077
- },
9078
- "gomorra": {
9079
- "id": "gomorra",
9080
- "title": "Gomorra",
9081
- "author": "Roberto Saviano",
9082
- "year": 2006,
9083
- "genre": "Inchiesta",
9084
- "summary": "Un reportage narrativo sulle economie e le violenze del sistema camorristico."
9085
- },
9086
- "oceano-mare": {
9087
- "id": "oceano-mare",
9088
- "title": "Oceano mare",
9089
- "author": "Alessandro Baricco",
9090
- "year": 1993,
9091
- "genre": "Romanzo letterario",
9092
- "summary": "Storie diverse si incontrano in una locanda sul mare, tra cura, naufragio e mistero."
9093
- },
9094
- "lessico-famigliare": {
9095
- "id": "lessico-famigliare",
9096
- "title": "Lessico famigliare",
9097
- "author": "Natalia Ginzburg",
9098
- "year": 1963,
9099
- "genre": "Memoria narrativa",
9100
- "summary": "Una famiglia prende forma attraverso parole, tic linguistici e memoria civile."
9101
- },
9102
- "cent-anni-di-solitudine": {
9103
- "id": "cent-anni-di-solitudine",
9104
- "title": "Cent'anni di solitudine",
9105
- "author": "Gabriel Garcia Marquez",
9106
- "year": 1967,
9107
- "genre": "Realismo magico",
9108
- "summary": "La saga dei Buendia e di Macondo intreccia mito, storia e destino."
9109
- },
9110
- "fahrenheit-451": {
9111
- "id": "fahrenheit-451",
9112
- "title": "Fahrenheit 451",
9113
- "author": "Ray Bradbury",
9114
- "year": 1953,
9115
- "genre": "Distopia",
9116
- "summary": "In un futuro dove i libri bruciano, leggere diventa un atto di resistenza."
9117
- },
9118
- "dune": {
9119
- "id": "dune",
9120
- "title": "Dune",
9121
- "author": "Frank Herbert",
9122
- "year": 1965,
9123
- "genre": "Fantascienza",
9124
- "summary": "Politica, ecologia e messianismo si scontrano sul pianeta desertico Arrakis."
9125
- },
9126
- "neuromancer": {
9127
- "id": "neuromancer",
9128
- "title": "Neuromancer",
9129
- "author": "William Gibson",
9130
- "year": 1984,
9131
- "genre": "Cyberpunk",
9132
- "summary": "Un hacker decaduto viene trascinato in un colpo che attraversa cyberspazio e intelligenze artificiali."
9133
- },
9134
- "il-signore-degli-anelli": {
9135
- "id": "il-signore-degli-anelli",
9136
- "title": "Il Signore degli Anelli",
9137
- "author": "J. R. R. Tolkien",
9138
- "year": 1954,
9139
- "genre": "Fantasy",
9140
- "summary": "La Compagnia affronta il potere dell'Anello in una delle grandi epopee moderne."
9141
- },
9142
- "harry-potter-e-la-pietra-filosofale": {
9143
- "id": "harry-potter-e-la-pietra-filosofale",
9144
- "title": "Harry Potter e la pietra filosofale",
9145
- "author": "J. K. Rowling",
9146
- "year": 1997,
9147
- "genre": "Fantasy",
9148
- "summary": "Un ragazzo scopre il mondo magico e il proprio posto in una storia piu grande."
9149
- },
9150
- "orgoglio-e-pregiudizio": {
9151
- "id": "orgoglio-e-pregiudizio",
9152
- "title": "Orgoglio e pregiudizio",
9153
- "author": "Jane Austen",
9154
- "year": 1813,
9155
- "genre": "Classico",
9156
- "summary": "Elizabeth Bennet e Mr. Darcy si misurano con classe, carattere e giudizio sociale."
9157
- },
9158
- "moby-dick": {
9159
- "id": "moby-dick",
9160
- "title": "Moby Dick",
9161
- "author": "Herman Melville",
9162
- "year": 1851,
9163
- "genre": "Avventura",
9164
- "summary": "La caccia alla balena bianca diventa ossessione metafisica e viaggio nell'abisso."
9165
- },
9166
- "delitto-e-castigo": {
9167
- "id": "delitto-e-castigo",
9168
- "title": "Delitto e castigo",
9169
- "author": "Fedor Dostoevskij",
9170
- "year": 1866,
9171
- "genre": "Romanzo psicologico",
9172
- "summary": "Raskolnikov attraversa colpa, febbre morale e possibilita di redenzione."
9173
- },
9174
- "anna-karenina": {
9175
- "id": "anna-karenina",
9176
- "title": "Anna Karenina",
9177
- "author": "Lev Tolstoj",
9178
- "year": 1877,
9179
- "genre": "Classico",
9180
- "summary": "Una storia d'amore e rovina dentro la societa russa dell'Ottocento."
9181
- },
9182
- "il-maestro-e-margherita": {
9183
- "id": "il-maestro-e-margherita",
9184
- "title": "Il maestro e Margherita",
9185
- "author": "Michail Bulgakov",
9186
- "year": 1967,
9187
- "genre": "Satira fantastica",
9188
- "summary": "Il diavolo visita Mosca in un romanzo visionario su arte, censura e amore."
9189
- },
9190
- "la-strada": {
9191
- "id": "la-strada",
9192
- "title": "La strada",
9193
- "author": "Cormac McCarthy",
9194
- "year": 2006,
9195
- "genre": "Post-apocalittico",
9196
- "summary": "Padre e figlio attraversano un mondo bruciato portando con se una fragile idea di bene."
9197
- },
9198
- "cloud-atlas": {
9199
- "id": "cloud-atlas",
9200
- "title": "Cloud Atlas",
9201
- "author": "David Mitchell",
9202
- "year": 2004,
9203
- "genre": "Romanzo corale",
9204
- "summary": "Sei storie in epoche diverse compongono una meditazione su potere, memoria e reincorrenza."
9205
- },
9206
- "kafka-sulla-spiaggia": {
9207
- "id": "kafka-sulla-spiaggia",
9208
- "title": "Kafka sulla spiaggia",
9209
- "author": "Haruki Murakami",
9210
- "year": 2002,
9211
- "genre": "Surrealismo",
9212
- "summary": "Fuga, destino e sogno si intrecciano in un romanzo sospeso tra reale e mitico."
9213
- },
9214
- "americanah": {
9215
- "id": "americanah",
9216
- "title": "Americanah",
9217
- "author": "Chimamanda Ngozi Adichie",
9218
- "year": 2013,
9219
- "genre": "Romanzo contemporaneo",
9220
- "summary": "Migrazione, razza e identita raccontate attraverso una storia d'amore tra Nigeria e Stati Uniti."
9221
- },
9222
- "la-casa-degli-spiriti": {
9223
- "id": "la-casa-degli-spiriti",
9224
- "title": "La casa degli spiriti",
9225
- "author": "Isabel Allende",
9226
- "year": 1982,
9227
- "genre": "Saga familiare",
9228
- "summary": "La storia della famiglia Trueba intreccia politica, memoria e realismo magico."
9229
- },
9230
- "beloved": {
9231
- "id": "beloved",
9232
- "title": "Beloved",
9233
- "author": "Toni Morrison",
9234
- "year": 1987,
9235
- "genre": "Romanzo storico",
9236
- "summary": "Il trauma della schiavitu ritorna come presenza viva nella casa di Sethe."
9237
- },
9238
- "la-breve-favolosa-vita-di-oscar-wao": {
9239
- "id": "la-breve-favolosa-vita-di-oscar-wao",
9240
- "title": "La breve favolosa vita di Oscar Wao",
9241
- "author": "Junot Diaz",
9242
- "year": 2007,
9243
- "genre": "Romanzo contemporaneo",
9244
- "summary": "Famiglia, diaspora dominicana e cultura pop si intrecciano nella storia di Oscar."
9245
- },
9246
- "le-correzioni": {
9247
- "id": "le-correzioni",
9248
- "title": "Le correzioni",
9249
- "author": "Jonathan Franzen",
9250
- "year": 2001,
9251
- "genre": "Romanzo familiare",
9252
- "summary": "Una famiglia americana tenta di ritrovarsi mentre ciascuno affronta le proprie fratture."
9253
- }
9316
+ {
9317
+ "1984": {
9318
+ "id": "1984",
9319
+ "title": "1984",
9320
+ "author": {
9321
+ "$ref": "../autori/autori.json#/george-orwell"
9322
+ },
9323
+ "year": 1949,
9324
+ "genre": "Distopia",
9325
+ "summary": "Un regime totalitario controlla linguaggio, memoria e pensiero."
9326
+ },
9327
+ "il-nome-della-rosa": {
9328
+ "id": "il-nome-della-rosa",
9329
+ "title": "Il nome della rosa",
9330
+ "author": {
9331
+ "$ref": "../autori/autori.json#/umberto-eco"
9332
+ },
9333
+ "year": 1980,
9334
+ "genre": "Romanzo storico",
9335
+ "summary": "Un'indagine in un'abbazia medievale diventa una riflessione su conoscenza, potere e interpretazione."
9336
+ },
9337
+ "se-questo-e-un-uomo": {
9338
+ "id": "se-questo-e-un-uomo",
9339
+ "title": "Se questo e un uomo",
9340
+ "author": {
9341
+ "$ref": "../autori/autori.json#/primo-levi"
9342
+ },
9343
+ "year": 1947,
9344
+ "genre": "Memoria",
9345
+ "summary": "La testimonianza essenziale di Levi sull'esperienza del lager e sulla dignita umana."
9346
+ },
9347
+ "le-citta-invisibili": {
9348
+ "id": "le-citta-invisibili",
9349
+ "title": "Le citta invisibili",
9350
+ "author": {
9351
+ "$ref": "../autori/autori.json#/italo-calvino"
9352
+ },
9353
+ "year": 1972,
9354
+ "genre": "Letteratura fantastica",
9355
+ "summary": "Marco Polo racconta a Kublai Khan citta immaginarie che parlano di memoria, desiderio e linguaggio."
9356
+ },
9357
+ "il-gattopardo": {
9358
+ "id": "il-gattopardo",
9359
+ "title": "Il Gattopardo",
9360
+ "author": {
9361
+ "$ref": "../autori/autori.json#/giuseppe-tomasi-di-lampedusa"
9362
+ },
9363
+ "year": 1958,
9364
+ "genre": "Romanzo storico",
9365
+ "summary": "Il tramonto dell'aristocrazia siciliana durante l'unificazione italiana."
9366
+ },
9367
+ "la-coscienza-di-zeno": {
9368
+ "id": "la-coscienza-di-zeno",
9369
+ "title": "La coscienza di Zeno",
9370
+ "author": {
9371
+ "$ref": "../autori/autori.json#/italo-svevo"
9372
+ },
9373
+ "year": 1923,
9374
+ "genre": "Romanzo psicologico",
9375
+ "summary": "Un diario ironico e nevrotico attraversa memoria, terapia e autoinganno."
9376
+ },
9377
+ "i-promessi-sposi": {
9378
+ "id": "i-promessi-sposi",
9379
+ "title": "I promessi sposi",
9380
+ "author": {
9381
+ "$ref": "../autori/autori.json#/alessandro-manzoni"
9382
+ },
9383
+ "year": 1842,
9384
+ "genre": "Classico",
9385
+ "summary": "La vicenda di Renzo e Lucia dentro carestia, guerra, peste e provvidenza."
9386
+ },
9387
+ "il-barone-rampante": {
9388
+ "id": "il-barone-rampante",
9389
+ "title": "Il barone rampante",
9390
+ "author": {
9391
+ "$ref": "../autori/autori.json#/italo-calvino"
9392
+ },
9393
+ "year": 1957,
9394
+ "genre": "Romanzo filosofico",
9395
+ "summary": "Cosimo sceglie di vivere sugli alberi e trasforma la distanza in una forma di liberta."
9396
+ },
9397
+ "gomorra": {
9398
+ "id": "gomorra",
9399
+ "title": "Gomorra",
9400
+ "author": {
9401
+ "$ref": "../autori/autori.json#/roberto-saviano"
9402
+ },
9403
+ "year": 2006,
9404
+ "genre": "Inchiesta",
9405
+ "summary": "Un reportage narrativo sulle economie e le violenze del sistema camorristico."
9406
+ },
9407
+ "oceano-mare": {
9408
+ "id": "oceano-mare",
9409
+ "title": "Oceano mare",
9410
+ "author": {
9411
+ "$ref": "../autori/autori.json#/alessandro-baricco"
9412
+ },
9413
+ "year": 1993,
9414
+ "genre": "Romanzo letterario",
9415
+ "summary": "Storie diverse si incontrano in una locanda sul mare, tra cura, naufragio e mistero."
9416
+ },
9417
+ "lessico-famigliare": {
9418
+ "id": "lessico-famigliare",
9419
+ "title": "Lessico famigliare",
9420
+ "author": {
9421
+ "$ref": "../autori/autori.json#/natalia-ginzburg"
9422
+ },
9423
+ "year": 1963,
9424
+ "genre": "Memoria narrativa",
9425
+ "summary": "Una famiglia prende forma attraverso parole, tic linguistici e memoria civile."
9426
+ },
9427
+ "cent-anni-di-solitudine": {
9428
+ "id": "cent-anni-di-solitudine",
9429
+ "title": "Cent'anni di solitudine",
9430
+ "author": {
9431
+ "$ref": "../autori/autori.json#/gabriel-garcia-marquez"
9432
+ },
9433
+ "year": 1967,
9434
+ "genre": "Realismo magico",
9435
+ "summary": "La saga dei Buendia e di Macondo intreccia mito, storia e destino."
9436
+ },
9437
+ "fahrenheit-451": {
9438
+ "id": "fahrenheit-451",
9439
+ "title": "Fahrenheit 451",
9440
+ "author": {
9441
+ "$ref": "../autori/autori.json#/ray-bradbury"
9442
+ },
9443
+ "year": 1953,
9444
+ "genre": "Distopia",
9445
+ "summary": "In un futuro dove i libri bruciano, leggere diventa un atto di resistenza."
9446
+ },
9447
+ "dune": {
9448
+ "id": "dune",
9449
+ "title": "Dune",
9450
+ "author": {
9451
+ "$ref": "../autori/autori.json#/frank-herbert"
9452
+ },
9453
+ "year": 1965,
9454
+ "genre": "Fantascienza",
9455
+ "summary": "Politica, ecologia e messianismo si scontrano sul pianeta desertico Arrakis."
9456
+ },
9457
+ "neuromancer": {
9458
+ "id": "neuromancer",
9459
+ "title": "Neuromancer",
9460
+ "author": {
9461
+ "$ref": "../autori/autori.json#/william-gibson"
9462
+ },
9463
+ "year": 1984,
9464
+ "genre": "Cyberpunk",
9465
+ "summary": "Un hacker decaduto viene trascinato in un colpo che attraversa cyberspazio e intelligenze artificiali."
9466
+ },
9467
+ "il-signore-degli-anelli": {
9468
+ "id": "il-signore-degli-anelli",
9469
+ "title": "Il Signore degli Anelli",
9470
+ "author": {
9471
+ "$ref": "../autori/autori.json#/j-r-r-tolkien"
9472
+ },
9473
+ "year": 1954,
9474
+ "genre": "Fantasy",
9475
+ "summary": "La Compagnia affronta il potere dell'Anello in una delle grandi epopee moderne."
9476
+ },
9477
+ "harry-potter-e-la-pietra-filosofale": {
9478
+ "id": "harry-potter-e-la-pietra-filosofale",
9479
+ "title": "Harry Potter e la pietra filosofale",
9480
+ "author": {
9481
+ "$ref": "../autori/autori.json#/j-k-rowling"
9482
+ },
9483
+ "year": 1997,
9484
+ "genre": "Fantasy",
9485
+ "summary": "Un ragazzo scopre il mondo magico e il proprio posto in una storia piu grande."
9486
+ },
9487
+ "orgoglio-e-pregiudizio": {
9488
+ "id": "orgoglio-e-pregiudizio",
9489
+ "title": "Orgoglio e pregiudizio",
9490
+ "author": {
9491
+ "$ref": "../autori/autori.json#/jane-austen"
9492
+ },
9493
+ "year": 1813,
9494
+ "genre": "Classico",
9495
+ "summary": "Elizabeth Bennet e Mr. Darcy si misurano con classe, carattere e giudizio sociale."
9496
+ },
9497
+ "moby-dick": {
9498
+ "id": "moby-dick",
9499
+ "title": "Moby Dick",
9500
+ "author": {
9501
+ "$ref": "../autori/autori.json#/herman-melville"
9502
+ },
9503
+ "year": 1851,
9504
+ "genre": "Avventura",
9505
+ "summary": "La caccia alla balena bianca diventa ossessione metafisica e viaggio nell'abisso."
9506
+ },
9507
+ "delitto-e-castigo": {
9508
+ "id": "delitto-e-castigo",
9509
+ "title": "Delitto e castigo",
9510
+ "author": {
9511
+ "$ref": "../autori/autori.json#/fedor-dostoevskij"
9512
+ },
9513
+ "year": 1866,
9514
+ "genre": "Romanzo psicologico",
9515
+ "summary": "Raskolnikov attraversa colpa, febbre morale e possibilita di redenzione."
9516
+ },
9517
+ "anna-karenina": {
9518
+ "id": "anna-karenina",
9519
+ "title": "Anna Karenina",
9520
+ "author": {
9521
+ "$ref": "../autori/autori.json#/lev-tolstoj"
9522
+ },
9523
+ "year": 1877,
9524
+ "genre": "Classico",
9525
+ "summary": "Una storia d'amore e rovina dentro la societa russa dell'Ottocento."
9526
+ },
9527
+ "il-maestro-e-margherita": {
9528
+ "id": "il-maestro-e-margherita",
9529
+ "title": "Il maestro e Margherita",
9530
+ "author": {
9531
+ "$ref": "../autori/autori.json#/michail-bulgakov"
9532
+ },
9533
+ "year": 1967,
9534
+ "genre": "Satira fantastica",
9535
+ "summary": "Il diavolo visita Mosca in un romanzo visionario su arte, censura e amore."
9536
+ },
9537
+ "la-strada": {
9538
+ "id": "la-strada",
9539
+ "title": "La strada",
9540
+ "author": {
9541
+ "$ref": "../autori/autori.json#/cormac-mccarthy"
9542
+ },
9543
+ "year": 2006,
9544
+ "genre": "Post-apocalittico",
9545
+ "summary": "Padre e figlio attraversano un mondo bruciato portando con se una fragile idea di bene."
9546
+ },
9547
+ "cloud-atlas": {
9548
+ "id": "cloud-atlas",
9549
+ "title": "Cloud Atlas",
9550
+ "author": {
9551
+ "$ref": "../autori/autori.json#/david-mitchell"
9552
+ },
9553
+ "year": 2004,
9554
+ "genre": "Romanzo corale",
9555
+ "summary": "Sei storie in epoche diverse compongono una meditazione su potere, memoria e reincorrenza."
9556
+ },
9557
+ "kafka-sulla-spiaggia": {
9558
+ "id": "kafka-sulla-spiaggia",
9559
+ "title": "Kafka sulla spiaggia",
9560
+ "author": {
9561
+ "$ref": "../autori/autori.json#/haruki-murakami"
9562
+ },
9563
+ "year": 2002,
9564
+ "genre": "Surrealismo",
9565
+ "summary": "Fuga, destino e sogno si intrecciano in un romanzo sospeso tra reale e mitico."
9566
+ },
9567
+ "americanah": {
9568
+ "id": "americanah",
9569
+ "title": "Americanah",
9570
+ "author": {
9571
+ "$ref": "../autori/autori.json#/chimamanda-ngozi-adichie"
9572
+ },
9573
+ "year": 2013,
9574
+ "genre": "Romanzo contemporaneo",
9575
+ "summary": "Migrazione, razza e identita raccontate attraverso una storia d'amore tra Nigeria e Stati Uniti."
9576
+ },
9577
+ "la-casa-degli-spiriti": {
9578
+ "id": "la-casa-degli-spiriti",
9579
+ "title": "La casa degli spiriti",
9580
+ "author": {
9581
+ "$ref": "../autori/autori.json#/isabel-allende"
9582
+ },
9583
+ "year": 1982,
9584
+ "genre": "Saga familiare",
9585
+ "summary": "La storia della famiglia Trueba intreccia politica, memoria e realismo magico."
9586
+ },
9587
+ "beloved": {
9588
+ "id": "beloved",
9589
+ "title": "Beloved",
9590
+ "author": {
9591
+ "$ref": "../autori/autori.json#/toni-morrison"
9592
+ },
9593
+ "year": 1987,
9594
+ "genre": "Romanzo storico",
9595
+ "summary": "Il trauma della schiavitu ritorna come presenza viva nella casa di Sethe."
9596
+ },
9597
+ "la-breve-favolosa-vita-di-oscar-wao": {
9598
+ "id": "la-breve-favolosa-vita-di-oscar-wao",
9599
+ "title": "La breve favolosa vita di Oscar Wao",
9600
+ "author": {
9601
+ "$ref": "../autori/autori.json#/junot-diaz"
9602
+ },
9603
+ "year": 2007,
9604
+ "genre": "Romanzo contemporaneo",
9605
+ "summary": "Famiglia, diaspora dominicana e cultura pop si intrecciano nella storia di Oscar."
9606
+ },
9607
+ "le-correzioni": {
9608
+ "id": "le-correzioni",
9609
+ "title": "Le correzioni",
9610
+ "author": {
9611
+ "$ref": "../autori/autori.json#/jonathan-franzen"
9612
+ },
9613
+ "year": 2001,
9614
+ "genre": "Romanzo familiare",
9615
+ "summary": "Una famiglia americana tenta di ritrovarsi mentre ciascuno affronta le proprie fratture."
9616
+ }
9254
9617
  }
9255
9618
  END_OF_FILE_CONTENT
9256
9619
  mkdir -p "src/data/config"
@@ -9570,11 +9933,70 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
9570
9933
  }
9571
9934
  END_OF_FILE_CONTENT
9572
9935
  mkdir -p "src/data/pages"
9573
- echo "Creating src/data/pages/home.json..."
9574
- cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
9936
+ mkdir -p "src/data/pages/authors"
9937
+ echo "Creating src/data/pages/authors.json..."
9938
+ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/authors.json"
9939
+ {
9940
+ "id": "authors-page",
9941
+ "slug": "authors",
9942
+ "meta": {
9943
+ "title": "Authors",
9944
+ "description": "Author directory powered by the autori collection."
9945
+ },
9946
+ "sections": [
9947
+ {
9948
+ "id": "authors-list-1",
9949
+ "type": "authors-list",
9950
+ "data": {
9951
+ "anchorId": "authors",
9952
+ "eyebrow": "Collection demo",
9953
+ "title": "Authors",
9954
+ "description": "A collection-backed directory of authors referenced by books.",
9955
+ "items": {
9956
+ "$ref": "../collections/autori/autori.json"
9957
+ }
9958
+ }
9959
+ }
9960
+ ],
9961
+ "global-header": false
9962
+ }
9963
+
9964
+ END_OF_FILE_CONTENT
9965
+ mkdir -p "src/data/pages/authors/[authorId]"
9966
+ echo "Creating src/data/pages/authors/[authorId]/libri.json..."
9967
+ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/authors/[authorId]/libri.json"
9968
+ {
9969
+ "id": "author-books-page",
9970
+ "slug": "authors/[authorId]/libri",
9971
+ "meta": {
9972
+ "title": "Libri per autore",
9973
+ "description": "Catalogo libri filtrato per autore."
9974
+ },
9975
+ "sections": [
9976
+ {
9977
+ "id": "books-list-author-filter",
9978
+ "type": "books-list",
9979
+ "data": {
9980
+ "anchorId": "libri-autore",
9981
+ "eyebrow": "Author books",
9982
+ "title": "Libri",
9983
+ "description": "Libri filtrati per autore dalla collection autori.",
9984
+ "items": {
9985
+ "$ref": "../../collections/libri/libri.json"
9986
+ },
9987
+ "pageSize": 10
9988
+ }
9989
+ }
9990
+ ],
9991
+ "global-header": false
9992
+ }
9993
+
9994
+ END_OF_FILE_CONTENT
9995
+ echo "Creating src/data/pages/form.json..."
9996
+ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/form.json"
9575
9997
  {
9576
- "id": "home-page",
9577
- "slug": "home",
9998
+ "id": "form-page",
9999
+ "slug": "form",
9578
10000
  "meta": {
9579
10001
  "title": "Home",
9580
10002
  "description": "OlonJS tenant alpha — form smoke test"
@@ -9597,6 +10019,34 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
9597
10019
  "global-header": false
9598
10020
  }
9599
10021
  END_OF_FILE_CONTENT
10022
+ echo "Creating src/data/pages/home.json..."
10023
+ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/home.json"
10024
+ {
10025
+ "id": "libri-page",
10026
+ "slug": "home",
10027
+ "meta": {
10028
+ "title": "Libri",
10029
+ "description": "Catalogo libri dimostrativo alimentato da COP collections."
10030
+ },
10031
+ "sections": [
10032
+ {
10033
+ "id": "books-list-1",
10034
+ "type": "books-list",
10035
+ "data": {
10036
+ "anchorId": "catalogo-libri",
10037
+ "eyebrow": "Collection demo",
10038
+ "title": "Libri",
10039
+ "description": "Una pagina collection con 30 titoli, paginazione lato componente e link alle schede dinamiche.",
10040
+ "items": {
10041
+ "$ref": "../collections/libri/libri.json"
10042
+ },
10043
+ "pageSize": 10
10044
+ }
10045
+ }
10046
+ ],
10047
+ "global-header": false
10048
+ }
10049
+ END_OF_FILE_CONTENT
9600
10050
  mkdir -p "src/data/pages/libri"
9601
10051
  echo "Creating src/data/pages/libri.json..."
9602
10052
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/libri.json"
@@ -9615,7 +10065,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/libri.json"
9615
10065
  "anchorId": "catalogo-libri",
9616
10066
  "eyebrow": "Collection demo",
9617
10067
  "title": "Libri",
9618
- "description": "Una pagina collection con 30 titoli, paginazione lato componente e link alle schede dinamiche.",
10068
+ "description": "Una pagina collection con titoli, paginazione lato componente e filtro autore.",
9619
10069
  "items": {
9620
10070
  "$ref": "../collections/libri/libri.json"
9621
10071
  },
@@ -9629,31 +10079,31 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/libri.json"
9629
10079
  END_OF_FILE_CONTENT
9630
10080
  echo "Creating src/data/pages/libri/[slug].json..."
9631
10081
  cat << 'END_OF_FILE_CONTENT' > "src/data/pages/libri/[slug].json"
9632
- {
9633
- "id": "libro-detail-page",
9634
- "slug": "libri/[slug]",
9635
- "meta": {
9636
- "title": "Dettaglio libro",
9637
- "description": "Pagina dinamica dettaglio libro alimentata dalla collection libri."
9638
- },
9639
- "sections": [
9640
- {
9641
- "id": "book-detail-1",
9642
- "type": "book-detail",
9643
- "data": {
9644
- "anchorId": "dettaglio-libro",
9645
- "item": {
9646
- "$ref": "collection:current"
9647
- },
9648
- "backLabel": "← Torna ai libri"
9649
- }
9650
- }
9651
- ],
9652
- "collection": {
9653
- "source": "libri",
9654
- "paramKey": "slug"
9655
- },
9656
- "global-header": false
10082
+ {
10083
+ "id": "libro-detail-page",
10084
+ "slug": "libri/[slug]",
10085
+ "meta": {
10086
+ "title": "Dettaglio libro",
10087
+ "description": "Pagina dinamica dettaglio libro alimentata dalla collection libri."
10088
+ },
10089
+ "sections": [
10090
+ {
10091
+ "id": "book-detail-1",
10092
+ "type": "book-detail",
10093
+ "data": {
10094
+ "anchorId": "dettaglio-libro",
10095
+ "item": {
10096
+ "$ref": "collection:current"
10097
+ },
10098
+ "backLabel": "← Torna ai libri"
10099
+ }
10100
+ }
10101
+ ],
10102
+ "collection": {
10103
+ "source": "libri",
10104
+ "paramKey": "slug"
10105
+ },
10106
+ "global-header": false
9657
10107
  }
9658
10108
  END_OF_FILE_CONTENT
9659
10109
  mkdir -p "src/emails"
@@ -11133,9 +11583,11 @@ END_OF_FILE_CONTENT
11133
11583
  mkdir -p "src/lib"
11134
11584
  echo "Creating src/lib/CollectionRegistry.ts..."
11135
11585
  cat << 'END_OF_FILE_CONTENT' > "src/lib/CollectionRegistry.ts"
11586
+ import { AutoriCollectionSchema } from '@/collections/autori';
11136
11587
  import { LibriCollectionSchema } from '@/collections/libri';
11137
11588
 
11138
11589
  export const CollectionRegistry = {
11590
+ autori: AutoriCollectionSchema,
11139
11591
  libri: LibriCollectionSchema,
11140
11592
  } as const;
11141
11593
 
@@ -11146,6 +11598,7 @@ echo "Creating src/lib/ComponentRegistry.tsx..."
11146
11598
  cat << 'END_OF_FILE_CONTENT' > "src/lib/ComponentRegistry.tsx"
11147
11599
  import type { SectionType } from '@/types';
11148
11600
  import type { SectionComponentPropsMap } from '@/types';
11601
+ import { AuthorsListView } from '@/components/authors-list';
11149
11602
  import { BookDetailView } from '@/components/book-detail';
11150
11603
  import { BooksListView } from '@/components/books-list';
11151
11604
  import { EmptyTenantView } from '@/components/empty-tenant';
@@ -11155,6 +11608,7 @@ import { FormDemoView } from '@/components/form-demo';
11155
11608
  export const ComponentRegistry: {
11156
11609
  [K in SectionType]: React.FC<SectionComponentPropsMap[K]>;
11157
11610
  } = {
11611
+ 'authors-list': AuthorsListView as React.FC<SectionComponentPropsMap['authors-list']>,
11158
11612
  'book-detail': BookDetailView as React.FC<SectionComponentPropsMap['book-detail']>,
11159
11613
  'books-list': BooksListView as React.FC<SectionComponentPropsMap['books-list']>,
11160
11614
  'empty-tenant': EmptyTenantView as React.FC<SectionComponentPropsMap['empty-tenant']>,
@@ -11227,9 +11681,10 @@ echo "Creating src/lib/addSectionConfig.ts..."
11227
11681
  cat << 'END_OF_FILE_CONTENT' > "src/lib/addSectionConfig.ts"
11228
11682
  import type { AddSectionConfig } from '@olonjs/core';
11229
11683
 
11230
- const addableSectionTypes = ['book-detail', 'books-list', 'empty-tenant', 'footer', 'form-demo'] as const;
11684
+ const addableSectionTypes = ['authors-list', 'book-detail', 'books-list', 'empty-tenant', 'footer', 'form-demo'] as const;
11231
11685
 
11232
11686
  const sectionTypeLabels: Record<string, string> = {
11687
+ 'authors-list': 'Authors List',
11233
11688
  'book-detail': 'Book Detail',
11234
11689
  'books-list': 'Books List',
11235
11690
  'empty-tenant': 'Empty Tenant',
@@ -11239,6 +11694,13 @@ const sectionTypeLabels: Record<string, string> = {
11239
11694
 
11240
11695
  function getDefaultSectionData(type: string): Record<string, unknown> {
11241
11696
  switch (type) {
11697
+ case 'authors-list':
11698
+ return {
11699
+ eyebrow: 'Collection demo',
11700
+ title: 'Authors',
11701
+ description: 'Authors loaded from the autori collection.',
11702
+ items: { $ref: '../collections/autori/autori.json' },
11703
+ };
11242
11704
  case 'book-detail':
11243
11705
  return {
11244
11706
  item: { $ref: 'collection:current' },
@@ -11312,6 +11774,49 @@ export function getHydratedData(
11312
11774
  };
11313
11775
  }
11314
11776
 
11777
+ END_OF_FILE_CONTENT
11778
+ echo "Creating src/lib/getFileCollections.ts..."
11779
+ cat << 'END_OF_FILE_CONTENT' > "src/lib/getFileCollections.ts"
11780
+ import type { JsonPagesConfig } from '@/types';
11781
+
11782
+ type CollectionDocuments = NonNullable<JsonPagesConfig['collections']>;
11783
+
11784
+ function collectionSourceFromPath(filePath: string): string | null {
11785
+ const normalizedPath = filePath.replace(/\\/g, '/');
11786
+ const match = normalizedPath.match(/\/data\/collections\/([^/]+)\/[^/]+\.json$/i);
11787
+ return match?.[1]?.trim() || null;
11788
+ }
11789
+
11790
+ export function getFileCollections(): CollectionDocuments {
11791
+ const glob = import.meta.glob<{ default: unknown }>('@/data/collections/**/*.json', { eager: true });
11792
+ const bySource = new Map<string, Record<string, unknown>>();
11793
+ const entries = Object.entries(glob).sort(([a], [b]) => a.localeCompare(b));
11794
+
11795
+ for (const [path, mod] of entries) {
11796
+ const source = collectionSourceFromPath(path);
11797
+ const raw = mod?.default;
11798
+ if (!source) {
11799
+ console.warn(`[tenant-alpha:getFileCollections] Ignoring collection module with invalid path "${path}".`);
11800
+ continue;
11801
+ }
11802
+ if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
11803
+ console.warn(`[tenant-alpha:getFileCollections] Ignoring invalid collection module at "${path}".`);
11804
+ continue;
11805
+ }
11806
+ if (bySource.has(source)) {
11807
+ console.warn(`[tenant-alpha:getFileCollections] Duplicate collection source "${source}" at "${path}". Keeping latest match.`);
11808
+ }
11809
+ bySource.set(source, raw as Record<string, unknown>);
11810
+ }
11811
+
11812
+ const collections: CollectionDocuments = {};
11813
+ for (const source of Array.from(bySource.keys()).sort((a, b) => a.localeCompare(b))) {
11814
+ const collection = bySource.get(source);
11815
+ if (collection) collections[source] = collection;
11816
+ }
11817
+ return collections;
11818
+ }
11819
+
11315
11820
  END_OF_FILE_CONTENT
11316
11821
  echo "Creating src/lib/getFilePages.ts..."
11317
11822
  cat << 'END_OF_FILE_CONTENT' > "src/lib/getFilePages.ts"
@@ -11363,6 +11868,7 @@ export function getFilePages(): Record<string, PageConfig> {
11363
11868
  END_OF_FILE_CONTENT
11364
11869
  echo "Creating src/lib/schemas.ts..."
11365
11870
  cat << 'END_OF_FILE_CONTENT' > "src/lib/schemas.ts"
11871
+ import { AuthorsListSchema } from '@/components/authors-list';
11366
11872
  import { BookDetailSchema } from '@/components/book-detail';
11367
11873
  import { BooksListSchema } from '@/components/books-list';
11368
11874
  import { EmptyTenantSchema } from '@/components/empty-tenant';
@@ -11370,6 +11876,7 @@ import { FooterSchema } from '@/components/footer';
11370
11876
  import { FormDemoSchema, FormDemoSubmissionSchema } from '@/components/form-demo';
11371
11877
 
11372
11878
  export const SECTION_SCHEMAS = {
11879
+ 'authors-list': AuthorsListSchema,
11373
11880
  'book-detail': BookDetailSchema,
11374
11881
  'books-list': BooksListSchema,
11375
11882
  'empty-tenant': EmptyTenantSchema,
@@ -11654,19 +12161,17 @@ cat << 'END_OF_FILE_CONTENT' > "src/runtime.ts"
11654
12161
  import type { JsonPagesConfig, MenuConfig, PageConfig, SiteConfig, ThemeConfig } from '@/types';
11655
12162
  import { CollectionRegistry } from '@/lib/CollectionRegistry';
11656
12163
  import { SECTION_SCHEMAS } from '@/lib/schemas';
12164
+ import { getFileCollections } from '@/lib/getFileCollections';
11657
12165
  import { getFilePages } from '@/lib/getFilePages';
11658
12166
  import siteData from '@/data/config/site.json';
11659
12167
  import menuData from '@/data/config/menu.json';
11660
12168
  import themeData from '@/data/config/theme.json';
11661
- import libriData from '@/data/collections/libri/libri.json';
11662
12169
 
11663
12170
  export const siteConfig = siteData as unknown as SiteConfig;
11664
12171
  export const themeConfig = themeData as unknown as ThemeConfig;
11665
12172
  export const menuConfig = menuData as unknown as MenuConfig;
11666
12173
  export const pages = getFilePages();
11667
- export const collections = {
11668
- libri: libriData as unknown as Record<string, unknown>,
11669
- } satisfies NonNullable<JsonPagesConfig['collections']>;
12174
+ export const collections = getFileCollections();
11670
12175
  export const collectionSchemas = CollectionRegistry as unknown as JsonPagesConfig['collectionSchemas'];
11671
12176
  export const refDocuments = {
11672
12177
  'menu.json': menuConfig,
@@ -11700,14 +12205,17 @@ END_OF_FILE_CONTENT
11700
12205
  # SKIP: src/runtime.ts:Zone.Identifier is binary and cannot be embedded as text.
11701
12206
  echo "Creating src/types.ts..."
11702
12207
  cat << 'END_OF_FILE_CONTENT' > "src/types.ts"
12208
+ import type { AuthorsListData, AuthorsListSettings } from '@/components/authors-list';
11703
12209
  import type { BookDetailData, BookDetailSettings } from '@/components/book-detail';
11704
12210
  import type { BooksListData, BooksListSettings } from '@/components/books-list';
11705
12211
  import type { EmptyTenantData, EmptyTenantSettings } from '@/components/empty-tenant';
11706
12212
  import type { FooterData, FooterSettings } from '@/components/footer';
11707
12213
  import type { FormDemoData, FormDemoSettings } from '@/components/form-demo';
12214
+ import type { Autore } from '@/collections/autori';
11708
12215
  import type { Libro } from '@/collections/libri';
11709
12216
 
11710
12217
  export type SectionComponentPropsMap = {
12218
+ 'authors-list': { data: AuthorsListData; settings?: AuthorsListSettings };
11711
12219
  'book-detail': { data: BookDetailData; settings?: BookDetailSettings };
11712
12220
  'books-list': { data: BooksListData; settings?: BooksListSettings };
11713
12221
  'empty-tenant': { data: EmptyTenantData; settings?: EmptyTenantSettings };
@@ -11717,6 +12225,7 @@ export type SectionComponentPropsMap = {
11717
12225
 
11718
12226
  declare module '@olonjs/core' {
11719
12227
  export interface SectionDataRegistry {
12228
+ 'authors-list': AuthorsListData;
11720
12229
  'book-detail': BookDetailData;
11721
12230
  'books-list': BooksListData;
11722
12231
  'empty-tenant': EmptyTenantData;
@@ -11724,6 +12233,7 @@ declare module '@olonjs/core' {
11724
12233
  'form-demo': FormDemoData;
11725
12234
  }
11726
12235
  export interface SectionSettingsRegistry {
12236
+ 'authors-list': AuthorsListSettings;
11727
12237
  'book-detail': BookDetailSettings;
11728
12238
  'books-list': BooksListSettings;
11729
12239
  'empty-tenant': EmptyTenantSettings;
@@ -11731,6 +12241,7 @@ declare module '@olonjs/core' {
11731
12241
  'form-demo': FormDemoSettings;
11732
12242
  }
11733
12243
  export interface CollectionItemRegistry {
12244
+ autori: Autore;
11734
12245
  libri: Libro;
11735
12246
  }
11736
12247
  }