Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions .vitepress/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,11 @@ export const navItemOrder: Record<string, string[]> = {
reference: ["api", "platform", "concepts", "infrastructure"],
"getting-started": ["quickstart", "core-concepts", "console"],
"app-shell": [
"index",
"introduction",
"quickstart",
"concepts",
"components",
"api",
"authentication",
"file-based-routing",
"module-resource-definition",
"routing-and-navigation",
"sidebar-navigation",
"styles",
"changelog",
],
};
Expand Down Expand Up @@ -90,13 +87,11 @@ export const defaultSidebarOrder: string[] = ["overview", "quickstart"];
export const sidebarItemOrder: Record<string, string[]> = {
function: ["overview", "builtin-interfaces"],
"app-shell": [
"introduction",
"quickstart",
"concepts",
"components",
"api",
"authentication",
"file-based-routing",
"module-resource-definition",
"routing-and-navigation",
"sidebar-navigation",
"styles",
"changelog",
],
};
Expand Down
17 changes: 12 additions & 5 deletions .vitepress/config/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export function generateSidebar(
// Sort function that respects custom order
const sortItems = (items: string[]) => {
return items.sort((a, b) => {
// Always put "overview" first
if (a.toLowerCase() === "overview") return -1;
if (b.toLowerCase() === "overview") return 1;

if (customOrder) {
const indexA = customOrder.indexOf(a);
const indexB = customOrder.indexOf(b);
Expand Down Expand Up @@ -74,8 +78,9 @@ export function generateSidebar(

// Process files first
for (const fileName of sortedFiles) {
const filePath = path.join(fullPath, fileName + ".md");
items.push({
text: toTitle(fileName + ".md"),
text: toTitle(fileName + ".md", { filePath }),
link: `${basePath}/${fileName}`,
});
}
Expand All @@ -87,8 +92,10 @@ export function generateSidebar(
const subItems = generateSidebar(docsDir, subdirPath, subdirBasePath, depth + 1);

if (subItems.length > 0) {
// Check if directory has an index.md to read title from
const dirIndexPath = path.join(fullPath, dirName, "index.md");
items.push({
text: toTitle(dirName),
text: toTitle(dirName, { filePath: fs.existsSync(dirIndexPath) ? dirIndexPath : undefined }),
collapsed: depth > 0,
items: subItems,
});
Expand All @@ -102,11 +109,11 @@ export function generateSidebar(
export function generateSectionSidebar(docsDir: string, section: string): SidebarItem[] {
const items = generateSidebar(docsDir, section, `/${section}`);

// Check if there's an index.md to add as overview
// Check if there's an index.md to add as overview (skip for app-shell)
const indexPath = path.join(docsDir, section, "index.md");
if (fs.existsSync(indexPath)) {
if (fs.existsSync(indexPath) && section !== "app-shell") {
items.unshift({
text: "Overview",
text: toTitle("index.md", { filePath: indexPath }),
link: `/${section}/`,
});
}
Expand Down
28 changes: 28 additions & 0 deletions .vitepress/config/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,28 @@ import fs from "node:fs";
export type ToTitleOptions = {
acronyms?: Record<string, string>; // token -> desired form
keepLowercase?: Set<string>; // optional: "and", "of", etc.
filePath?: string; // optional: full file path to read frontmatter
};

// Extract title from markdown frontmatter
function extractFrontmatterTitle(filePath: string): string | null {
try {
if (!fs.existsSync(filePath)) return null;

const content = fs.readFileSync(filePath, "utf-8");
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);

if (!frontmatterMatch) return null;

const frontmatter = frontmatterMatch[1];
const titleMatch = frontmatter.match(/^title:\s*(.+)$/m);

return titleMatch ? titleMatch[1].trim() : null;
} catch {
return null;
}
}

// Helper to get a readable title from filename or directory name
export function toTitle(input: string, options: ToTitleOptions = {}): string {
// Check for custom title override first
Expand All @@ -14,6 +34,14 @@ export function toTitle(input: string, options: ToTitleOptions = {}): string {
return customTitles[normalizedInput];
}

// Try to read title from frontmatter if file path is provided
if (options.filePath) {
const frontmatterTitle = extractFrontmatterTitle(options.filePath);
if (frontmatterTitle) {
return frontmatterTitle;
}
}

const allAcronyms: Record<string, string> = {
...acronyms,
...options.acronyms,
Expand Down
44 changes: 40 additions & 4 deletions docs/app-shell/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
# AppShell
---
title: Introduction
description: AppShell is an opinionated React application framework for creating applications on Tailor Platform with built-in authentication, routing, and beautiful UI components.
---

AppShell is a UI framework for building interfaces on Tailor Platform.
# Introduction

## Overview
> **AppShell gives you everything you need to build production-ready ERP applications on Tailor Platform with minimal configuration.**

AppShell provides components and utilities for building modern admin panels and dashboards.
## What is AppShell?

AppShell is a React-based framework that provides the foundation for building custom ERP applications on Tailor Platform. It handles the complex infrastructure so you can focus on building business-level screens and features.

## Out of the Box

When you integrate AppShell into your React application, you get:

- ✅ **Simple composition** - Compose custom Tailor-powered, React-based ERP applications with minimal code
- ✅ **User authentication** - Complete OAuth2 flow with token management and session persistence
- ✅ **Authorization** - Route guards for permission-based and role-based access control
- ✅ **File-based routing** - Define pages through directory structure with auto-generated navigation
- ✅ **Beautiful layouts** - Responsive, opinionated layouts with sidebar navigation and breadcrumbs
- ✅ **Convenience hooks** - Access application, module, and user contexts with React hooks

## Key Features

### 🚀 File-based Routing

Define pages through directory structure with automatic sidebar navigation and breadcrumbs. No manual route configuration needed.

→ [Learn about File-Based Routing](concepts/file-based-routing)

### 🔒 Built-in Authentication

OAuth2/OIDC integration with Tailor Platform's Auth service. Supports any configured IdP including Google, Okta, and Auth0.

→ [Authentication Guide](concepts/authentication)

### 🎨 Beautiful UI Components

Responsive layouts, description cards, command palette, and more. Built with Tailwind CSS v4 and shadcn/ui principles.

→ [Quick Start](quickstart)
43 changes: 0 additions & 43 deletions docs/app-shell/introduction.md

This file was deleted.

17 changes: 15 additions & 2 deletions scripts/docs-sync/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ for (const file of config.removeFiles) {
fs.rmSync(path.join(config.dst, file), { force: true });
}

// 4. Restore index.md
if (indexBackup) {
// 4. Restore index.md (skip for app-shell as we'll use introduction.md)
if (indexBackup && configName !== "app-shell") {
fs.writeFileSync(indexBackupPath, indexBackup, "utf8");
}

Expand All @@ -158,4 +158,17 @@ walk(config.dst)
fs.writeFileSync(f, content);
});

// 7. For app-shell: rename introduction.md to index.md
if (configName === "app-shell") {
const introPath = path.join(config.dst, "introduction.md");
const indexPath = path.join(config.dst, "index.md");

if (fs.existsSync(introPath)) {
fs.renameSync(introPath, indexPath);
console.log("Renamed introduction.md to index.md");
} else {
console.warn("introduction.md not found, skipping rename");
}
}

console.log(`${config.label} docs synced`);