docs: add design document for enhanced download UI
Add comprehensive design document for project aliases and APK environment classification feature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
192
docs/plans/2026-03-04-enhance-download-ui-design.md
Normal file
192
docs/plans/2026-03-04-enhance-download-ui-design.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# Design: Enhanced Download UI Readability
|
||||
|
||||
**Date:** 2026-03-04
|
||||
**Status:** Approved
|
||||
|
||||
## Overview
|
||||
|
||||
Enhance the download UI to improve readability by:
|
||||
1. Displaying project aliases instead of directory names
|
||||
2. Classifying APKs by environment (dev, sandbox, product, other)
|
||||
3. Displaying APKs in a list format with commit ID, file size, and creation time
|
||||
4. Organizing APKs into tabs by environment
|
||||
|
||||
## Requirements
|
||||
|
||||
### Project Aliases
|
||||
- Store project display name mappings in `lib/project-aliases.json`
|
||||
- Example: `FT` → `外放构建`, `FT_DEV` → `开发构建`
|
||||
- Fallback: Display directory name if no alias exists
|
||||
|
||||
### APK Classification
|
||||
- Categories: `dev`, `sandbox`, `product`, `other`
|
||||
- APK filename format: `ft_[environment]_[version]_[commit_id].apk`
|
||||
- Environment suffix is optional; if missing, default to `product`
|
||||
- Unmatched formats go to `other` tab
|
||||
|
||||
### Display Format
|
||||
- List view with columns: Commit ID, File Size, Creation Time, Download
|
||||
- Always display all 4 tabs (dev, product, sandbox, other)
|
||||
- Commit filter applies globally to all tabs
|
||||
|
||||
## Architecture
|
||||
|
||||
### New Files
|
||||
- `lib/project-aliases.json` - Project name alias configuration
|
||||
- `lib/apk-parser.ts` - APK filename parsing utilities
|
||||
|
||||
### Modified Files
|
||||
- `lib/utils.ts` - Add alias lookup utility
|
||||
- `app/api/projects/route.ts` - Return projects with aliases
|
||||
- `app/api/apks/route.ts` - Add environment metadata to response
|
||||
- `app/download/SearchForm.tsx` - Use project aliases in dropdown
|
||||
- `app/download/ApkList.tsx` - Replace card grid with tabbed list view
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. **Project Selection:**
|
||||
- Client loads projects → API reads `project-aliases.json` → returns with display names
|
||||
- `SearchForm` displays `displayName`, submits `name` to API
|
||||
|
||||
2. **APK Fetching:**
|
||||
- User selects project/version → client fetches APKs
|
||||
- API uses `parseApkFilename()` to extract environment
|
||||
- Returns enriched APKs with `environment` field
|
||||
|
||||
3. **Display:**
|
||||
- Client receives APKs with `environment` field
|
||||
- Organizes into tabs (dev, product, sandbox, other)
|
||||
- Displays in list format
|
||||
|
||||
## Data Structures
|
||||
|
||||
### APK Parser Types
|
||||
```typescript
|
||||
export type ApkEnvironment = 'dev' | 'sandbox' | 'product' | 'other';
|
||||
|
||||
export interface ParsedApkMetadata {
|
||||
environment: ApkEnvironment;
|
||||
commit: string | null;
|
||||
isValid: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### Project Alias Config
|
||||
```json
|
||||
{
|
||||
"FT": "外放构建",
|
||||
"FT_DEV": "开发构建"
|
||||
}
|
||||
```
|
||||
|
||||
### Enriched APK Response
|
||||
```typescript
|
||||
{
|
||||
name: string;
|
||||
environment: ApkEnvironment;
|
||||
commit: string | null;
|
||||
size: number;
|
||||
modifiedAt: string; // ISO string
|
||||
downloadUrl: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### ApkList (Restructured)
|
||||
- Replaces grid of `ApkCard` with tabbed list view
|
||||
- Manages tab state (active tab, filtered APKs)
|
||||
- Handles commit filter globally
|
||||
- Delegates list display to `ApkTable` component
|
||||
|
||||
### ApkTable (New)
|
||||
- Table with columns: Commit ID, File Size, Creation Time, Download
|
||||
- Styled with hover effects
|
||||
- Empty state handling per tab
|
||||
|
||||
### SearchForm (Updated)
|
||||
- Uses `getProjectDisplayName()` for project dropdown
|
||||
- Maintains existing functionality
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Project Alias Edge Cases
|
||||
- Missing alias file: Fallback to directory names
|
||||
- Project not in aliases: Display directory name
|
||||
- Malformed JSON: Log error, fallback to directory names
|
||||
|
||||
### APK Filename Parsing Edge Cases
|
||||
- No environment in filename: Default to `product`
|
||||
- No commit ID: `commit` field is `null`
|
||||
- Unmatched format: `environment: "other"`, still display
|
||||
- Invalid characters: Handle gracefully
|
||||
|
||||
### UI Edge Cases
|
||||
- Empty tab: Display "暂无 APK 文件"
|
||||
- All tabs empty: Show helpful message
|
||||
- Loading state: Skeleton or spinner
|
||||
- API error: Display error message, allow retry
|
||||
|
||||
## APK Filename Parsing Logic
|
||||
|
||||
### Regex Pattern
|
||||
```typescript
|
||||
const APK_PATTERN = /^ft_([a-z]+)?_([0-9_]+)_([a-f0-9]{7,})\.apk$/;
|
||||
// ^^^ ^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^
|
||||
// ft env(opt) version commit id
|
||||
```
|
||||
|
||||
### Parsing Rules
|
||||
1. Try to match standard pattern: `ft_[env]_[version]_[commit].apk`
|
||||
2. Extract environment if present, otherwise default to `product`
|
||||
3. If pattern doesn't match, assign `environment: "other"`
|
||||
4. Extract commit ID from filename
|
||||
5. Use file system metadata for size and modified time
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- `lib/__tests__/apk-parser.test.ts`: Test filename parsing for all environments
|
||||
- `lib/__tests__/project-aliases.test.ts`: Test alias lookup logic
|
||||
|
||||
### Integration Tests
|
||||
- API tests for `/api/projects` with alias data
|
||||
- API tests for `/api/apks` returning enriched metadata
|
||||
|
||||
### Component Tests
|
||||
- `ApkTable`: Renders correct columns, handles empty states
|
||||
- `ApkList`: Tabs work correctly, filters apply globally
|
||||
- `SearchForm`: Displays project aliases correctly
|
||||
|
||||
### Manual Testing Checklist
|
||||
- [ ] Projects display with aliases (FT → 外放构建)
|
||||
- [ ] Projects without aliases show directory names
|
||||
- [ ] APKs grouped correctly in 4 tabs
|
||||
- [ ] Product APKs (no env suffix) go to "product" tab
|
||||
- [ ] Dev/sandbox APKs go to correct tabs
|
||||
- [ ] Invalid format APKs go to "other" tab
|
||||
- [ ] Commit filter applies to all tabs
|
||||
- [ ] List shows Commit ID, File Size, Creation Time
|
||||
- [ ] Download button works correctly
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
**Selected:** Approach 2 - Shared Utility Functions + Client-side Organization
|
||||
|
||||
**Why:**
|
||||
- Clean separation of concerns
|
||||
- Reusable parsing utilities
|
||||
- Type-safe with TypeScript
|
||||
- Easier to unit test parsing logic
|
||||
- Client receives all APKs once, then handles tab organization
|
||||
- Performance overhead is minimal for this use case
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. Project aliases display correctly in dropdown
|
||||
2. APKs are correctly classified by environment
|
||||
3. All 4 tabs display correctly (dev, product, sandbox, other)
|
||||
4. List view shows correct columns (Commit ID, File Size, Creation Time)
|
||||
5. Commit filter works globally across all tabs
|
||||
6. Edge cases handled gracefully (missing aliases, invalid formats)
|
||||
7. All tests pass
|
||||
Reference in New Issue
Block a user