[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6KKB7FSKX3SNNE781X3P7T2":3,"skill-same:01M6KKB7FSKX3SNNE781X3P7T2":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":43,"commitSha":44,"skillDirectory":45,"forksCount":13,"repositoryHtmlUrl":46,"repositoryHomepageUrl":6,"ownerHtmlUrl":47,"ownerBlogUrl":6,"ownerDescription":48,"updatedAt":49,"install":50,"resources":63},"01M6KKB7FSKX3SNNE781X3P7T2","dynamic-panel-component",null,"Create a dashboard panel component with advanced dynamic content updates and robust error handling, including retry logic and state management, using vanilla TypeScript DOM API.","AG064\u002FOpenSpace","AG064","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F184930299?v=4","01M6KHVFG7TB73NHX3RDYSTMH2","01M6KHVFMM7XA2PZA9Z83PHS3V",0,"MIT","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fdynamic-panel-component\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"panel component","component","dynamic panel component","panel","logic","updates","dom","retry","advanced","with","error","typescript","management","api","content","state","error handling","state management","create","dashboard","2026-11-10 10:12:30",false,"---\nname: dynamic-panel-component\ndescription: Create a dashboard panel component with advanced dynamic content updates and robust error handling, including retry logic and state management, using vanilla TypeScript DOM API.\n---\n\n# Dynamic Panel Component Pattern\n\nCreate dashboard panel components using **vanilla TypeScript** (no framework, no JSX). Each panel is a class extending a `Panel` base class, with enhanced dynamic content updates and error handling.\n\n## Architecture Overview\n\n```\nPanel (base class)\n├── element: HTMLElement (outer container, .panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional]\n│   │   ├── statusBadge (.panel-data-badge) [optional]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   └── resizeHandle (.panel-resize-handle)\n```\n\n## Base Panel Class (Enhanced)\n\nCreate `src\u002Fcomponents\u002FPanel.ts`:\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n  private _errorState = false;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className || ''}`;\n    this.element.dataset.panel = options.id;\n\n    \u002F\u002F Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    \u002F\u002F Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    \u002F\u002F Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this._errorState = false;\n    this.content.innerHTML = `\n     \u003Cdiv class=\"panel-loading\">\n        \u003Cdiv class=\"panel-loading-spinner\">\u003C\u002Fdiv>\n        \u003Cdiv class=\"panel-loading-text\">${message}\u003C\u002Fdiv>\n      \u003C\u002Fdiv>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this._errorState = true;\n    this.content.innerHTML = `\n      \u003Cdiv class=\"panel-error-state\">\n        \u003Cdiv class=\"panel-error-msg\">${message}\u003C\u002Fdiv>\n        ${onRetry ? '\u003Cbutton class=\"panel-retry-btn\" data-panel-retry>Retry\u003C\u002Fbutton>' : ''}\n      \u003C\u002Fdiv>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void {\n    this._errorState = false;\n    this.content.innerHTML = html;\n  }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n  protected get isErrorState(): boolean { return this._errorState; }\n\n  public destroy(): void {\n    this.element.remove();\n  }\n}\n```\n\n## Creating a Concrete Panel (Example: StockPanel with Enhanced Error Handling)\n\n```typescript\nimport { Panel } from '.\u002FPanel';\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType\u003Ctypeof setInterval> | null = null;\n  private retryCount = 0;\n  private maxRetries = 3;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n  }\n\n  private async fetchData(): Promise\u003Cvoid> {\n    if (this.isFetching || this.isErrorState) return;\n    this.setFetching(true);\n    try {\n      const quotes = await fetchStockQuotes(); \u002F\u002F from data-service\n      this.render(quotes);\n      this.setCount(quotes.length);\n      this.retryCount = 0; \u002F\u002F Reset retry count on success\n    } catch (err) {\n      this.retryCount++;\n      if (this.retryCount \u003C this.maxRetries) {\n        setTimeout(() => this.fetchData(), 1000 * this.retryCount); \u002F\u002F Exponential backoff\n      } else {\n        this.showError('Failed to load stock data', () => {\n          this.retryCount = 0;\n          this.fetchData();\n        });\n      }\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(quotes: StockQuote[]): void {\n    const rows = quotes.map(q => `\n      \u003Cdiv class=\"stock-row\">\n        \u003Cspan class=\"stock-symbol\">${q.symbol}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-name\">${q.name}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-price\">${q.price != null ? '$' + q.price.toFixed(2) : '—'}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}\n        \u003C\u002Fspan>\n        ${miniSparkline(q.sparkline, q.change)}\n      \u003C\u002Fdiv>\n    `).join('');\n\n    this.setContent(`\u003Cdiv class=\"stock-list\">${rows}\u003C\u002Fdiv>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n## Key Patterns\n\n1. **Constructor** calls `super()` with panel config, then triggers initial data fetch.\n2. **fetchData()** includes retry logic with exponential backoff and max retry limit.\n3. **render()** builds HTML strings and calls `this.setContent(html)`.\n4. **destroy()** cleans up timers and event listeners.\n5. Use `showLoading()` during initial load (auto-called in constructor).\n6. Use `showError(msg, retryFn)` on failure with retry button.\n7. Track error state with `_errorState` to prevent redundant fetches.\n8. Sparkline SVGs use inline `\u003Csvg>` with `\u003Cpolyline>` — see sparkline utility.\n\n## Sparkline Utility\n\n```typescript\nexport function miniSparkline(data: number[] | undefined, change: number | null, w = 50, h = 16): string {\n  if (!data || data.length \u003C 2) return '';\n  const min = Math.min(...data);\n  const max = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i \u002F (data.length - 1)) * w;\n    const y = h - ((v - min) \u002F range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `\u003Csvg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">\u003Cpolyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u002F>\u003C\u002Fsvg>`;\n}\n```\n\n## Additional Examples\n\n### Dynamic Content Update Example\n\n```typescript\nexport class NewsPanel extends Panel {\n  private newsItems: NewsItem[] = [];\n\n  constructor() {\n    super({ id: 'news', title: 'Latest News' });\n    this.fetchNews();\n    setInterval(() => this.fetchNews(), 300_000); \u002F\u002F Update every 5 minutes\n  }\n\n  private async fetchNews(): Promise\u003Cvoid> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const news = await fetchNewsItems();\n      this.newsItems = news;\n      this.renderNews();\n    } catch (err) {\n      this.showError('Failed to load news', () => this.fetchNews());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderNews(): void {\n    const items = this.newsItems.map(item => `\n      \u003Cdiv class=\"news-item\">\n        \u003Ch3 class=\"news-title\">${item.title}\u003C\u002Fh3>\n        \u003Cp class=\"news-summary\">${item.summary}\u003C\u002Fp>\n        \u003Cspan class=\"news-time\">${formatTime(item.time)}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    `).join('');\n    this.setContent(`\u003Cdiv class=\"news-list\">${items}\u003C\u002Fdiv>`);\n  }\n}\n```\n\n### Error Handling with State Management\n\n```typescript\nexport class WeatherPanel extends Panel {\n  private lastSuccessfulFetch: Date | null = null;\n\n  constructor() {\n    super({ id: 'weather', title: 'Weather Forecast' });\n    this.fetchWeather();\n  }\n\n  private async fetchWeather(): Promise\u003Cvoid> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const weather = await fetchWeatherData();\n      this.lastSuccessfulFetch = new Date();\n      this.renderWeather(weather);\n    } catch (err) {\n      const message = this.lastSuccessfulFetch\n        ? `Failed to update (last success: ${this.lastSuccessfulFetch.toLocaleTimeString()})`\n        : 'Failed to load initial data';\n      this.showError(message, () => this.fetchWeather());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderWeather(weather: WeatherData): void {\n    const html = `\n      \u003Cdiv class=\"weather-current\">\n        \u003Cspan class=\"weather-temp\">${weather.temp}°\u003C\u002Fspan>\n        \u003Cspan class=\"weather-desc\">${weather.description}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"weather-forecast\">\n        ${weather.forecast.map(day => `\n          \u003Cdiv class=\"weather-day\">\n            \u003Cspan class=\"weather-day-name\">${day.day}\u003C\u002Fspan>\n            \u003Cspan class=\"weather-day-temp\">${day.high}°\u002F${day.low}°\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n        `).join('')}\n      \u003C\u002Fdiv>\n    `;\n    this.setContent(html);\n  }\n}\n```\n","# Dynamic Panel Component Pattern\n\nCreate dashboard panel components using **vanilla TypeScript** (no framework, no JSX). Each panel is a class extending a `Panel` base class, with enhanced dynamic content updates and error handling.\n\n## Architecture Overview\n\n```\nPanel (base class)\n├── element: HTMLElement (outer container, .panel)\n│   ├── header: HTMLElement (.panel-header)\n│   │   ├── headerLeft (.panel-header-left)\n│   │   │   ├── title (.panel-title)\n│   │   │   └── newBadge (.panel-new-badge) [optional]\n│   │   ├── statusBadge (.panel-data-badge) [optional]\n│   │   └── countEl (.panel-count) [optional]\n│   ├── content: HTMLElement (.panel-content)\n│   └── resizeHandle (.panel-resize-handle)\n```\n\n## Base Panel Class (Enhanced)\n\nCreate `src\u002Fcomponents\u002FPanel.ts`:\n\n```typescript\nexport interface PanelOptions {\n  id: string;\n  title: string;\n  showCount?: boolean;\n  className?: string;\n}\n\nexport class Panel {\n  protected element: HTMLElement;\n  protected content: HTMLElement;\n  protected header: HTMLElement;\n  protected countEl: HTMLElement | null = null;\n  protected panelId: string;\n  private _fetching = false;\n  private _errorState = false;\n\n  constructor(options: PanelOptions) {\n    this.panelId = options.id;\n    this.element = document.createElement('div');\n    this.element.className = `panel ${options.className || ''}`;\n    this.element.dataset.panel = options.id;\n\n    \u002F\u002F Header\n    this.header = document.createElement('div');\n    this.header.className = 'panel-header';\n\n    const headerLeft = document.createElement('div');\n    headerLeft.className = 'panel-header-left';\n\n    const title = document.createElement('span');\n    title.className = 'panel-title';\n    title.textContent = options.title;\n    headerLeft.appendChild(title);\n    this.header.appendChild(headerLeft);\n\n    \u002F\u002F Count badge (optional)\n    if (options.showCount) {\n      this.countEl = document.createElement('span');\n      this.countEl.className = 'panel-count';\n      this.countEl.textContent = '0';\n      this.header.appendChild(this.countEl);\n    }\n\n    \u002F\u002F Content area\n    this.content = document.createElement('div');\n    this.content.className = 'panel-content';\n    this.content.id = `${options.id}Content`;\n\n    this.element.appendChild(this.header);\n    this.element.appendChild(this.content);\n    this.showLoading();\n  }\n\n  public getElement(): HTMLElement { return this.element; }\n\n  public showLoading(message = 'Loading...'): void {\n    this._errorState = false;\n    this.content.innerHTML = `\n     \u003Cdiv class=\"panel-loading\">\n        \u003Cdiv class=\"panel-loading-spinner\">\u003C\u002Fdiv>\n        \u003Cdiv class=\"panel-loading-text\">${message}\u003C\u002Fdiv>\n      \u003C\u002Fdiv>`;\n  }\n\n  public showError(message = 'Failed to load', onRetry?: () => void): void {\n    this._errorState = true;\n    this.content.innerHTML = `\n      \u003Cdiv class=\"panel-error-state\">\n        \u003Cdiv class=\"panel-error-msg\">${message}\u003C\u002Fdiv>\n        ${onRetry ? '\u003Cbutton class=\"panel-retry-btn\" data-panel-retry>Retry\u003C\u002Fbutton>' : ''}\n      \u003C\u002Fdiv>`;\n    if (onRetry) {\n      this.content.querySelector('[data-panel-retry]')?.addEventListener('click', onRetry);\n    }\n  }\n\n  public setContent(html: string): void {\n    this._errorState = false;\n    this.content.innerHTML = html;\n  }\n\n  public setCount(count: number): void {\n    if (this.countEl) this.countEl.textContent = count.toString();\n  }\n\n  public show(): void { this.element.classList.remove('hidden'); }\n  public hide(): void { this.element.classList.add('hidden'); }\n\n  protected setFetching(v: boolean): void { this._fetching = v; }\n  protected get isFetching(): boolean { return this._fetching; }\n  protected get isErrorState(): boolean { return this._errorState; }\n\n  public destroy(): void {\n    this.element.remove();\n  }\n}\n```\n\n## Creating a Concrete Panel (Example: StockPanel with Enhanced Error Handling)\n\n```typescript\nimport { Panel } from '.\u002FPanel';\n\ninterface StockQuote {\n  symbol: string;\n  name: string;\n  price: number | null;\n  change: number | null;\n  sparkline?: number[];\n}\n\nexport class StockPanel extends Panel {\n  private refreshTimer: ReturnType\u003Ctypeof setInterval> | null = null;\n  private retryCount = 0;\n  private maxRetries = 3;\n\n  constructor() {\n    super({ id: 'stocks', title: 'Stock Market', showCount: true });\n    this.fetchData();\n    this.refreshTimer = setInterval(() => this.fetchData(), 60_000);\n  }\n\n  private async fetchData(): Promise\u003Cvoid> {\n    if (this.isFetching || this.isErrorState) return;\n    this.setFetching(true);\n    try {\n      const quotes = await fetchStockQuotes(); \u002F\u002F from data-service\n      this.render(quotes);\n      this.setCount(quotes.length);\n      this.retryCount = 0; \u002F\u002F Reset retry count on success\n    } catch (err) {\n      this.retryCount++;\n      if (this.retryCount \u003C this.maxRetries) {\n        setTimeout(() => this.fetchData(), 1000 * this.retryCount); \u002F\u002F Exponential backoff\n      } else {\n        this.showError('Failed to load stock data', () => {\n          this.retryCount = 0;\n          this.fetchData();\n        });\n      }\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private render(quotes: StockQuote[]): void {\n    const rows = quotes.map(q => `\n      \u003Cdiv class=\"stock-row\">\n        \u003Cspan class=\"stock-symbol\">${q.symbol}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-name\">${q.name}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-price\">${q.price != null ? '$' + q.price.toFixed(2) : '—'}\u003C\u002Fspan>\n        \u003Cspan class=\"stock-change ${(q.change ?? 0) >= 0 ? 'positive' : 'negative'}\">\n          ${q.change != null ? (q.change >= 0 ? '+' : '') + q.change.toFixed(2) + '%' : '—'}\n        \u003C\u002Fspan>\n        ${miniSparkline(q.sparkline, q.change)}\n      \u003C\u002Fdiv>\n    `).join('');\n\n    this.setContent(`\u003Cdiv class=\"stock-list\">${rows}\u003C\u002Fdiv>`);\n  }\n\n  public override destroy(): void {\n    if (this.refreshTimer) clearInterval(this.refreshTimer);\n    super.destroy();\n  }\n}\n```\n\n## Key Patterns\n\n1. **Constructor** calls `super()` with panel config, then triggers initial data fetch.\n2. **fetchData()** includes retry logic with exponential backoff and max retry limit.\n3. **render()** builds HTML strings and calls `this.setContent(html)`.\n4. **destroy()** cleans up timers and event listeners.\n5. Use `showLoading()` during initial load (auto-called in constructor).\n6. Use `showError(msg, retryFn)` on failure with retry button.\n7. Track error state with `_errorState` to prevent redundant fetches.\n8. Sparkline SVGs use inline `\u003Csvg>` with `\u003Cpolyline>` — see sparkline utility.\n\n## Sparkline Utility\n\n```typescript\nexport function miniSparkline(data: number[] | undefined, change: number | null, w = 50, h = 16): string {\n  if (!data || data.length \u003C 2) return '';\n  const min = Math.min(...data);\n  const max = Math.max(...data);\n  const range = max - min || 1;\n  const color = change != null && change >= 0 ? 'var(--green)' : 'var(--red)';\n  const points = data.map((v, i) => {\n    const x = (i \u002F (data.length - 1)) * w;\n    const y = h - ((v - min) \u002F range) * (h - 2) - 1;\n    return `${x.toFixed(1)},${y.toFixed(1)}`;\n  }).join(' ');\n  return `\u003Csvg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\">\u003Cpolyline points=\"${points}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\u002F>\u003C\u002Fsvg>`;\n}\n```\n\n## Additional Examples\n\n### Dynamic Content Update Example\n\n```typescript\nexport class NewsPanel extends Panel {\n  private newsItems: NewsItem[] = [];\n\n  constructor() {\n    super({ id: 'news', title: 'Latest News' });\n    this.fetchNews();\n    setInterval(() => this.fetchNews(), 300_000); \u002F\u002F Update every 5 minutes\n  }\n\n  private async fetchNews(): Promise\u003Cvoid> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const news = await fetchNewsItems();\n      this.newsItems = news;\n      this.renderNews();\n    } catch (err) {\n      this.showError('Failed to load news', () => this.fetchNews());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderNews(): void {\n    const items = this.newsItems.map(item => `\n      \u003Cdiv class=\"news-item\">\n        \u003Ch3 class=\"news-title\">${item.title}\u003C\u002Fh3>\n        \u003Cp class=\"news-summary\">${item.summary}\u003C\u002Fp>\n        \u003Cspan class=\"news-time\">${formatTime(item.time)}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    `).join('');\n    this.setContent(`\u003Cdiv class=\"news-list\">${items}\u003C\u002Fdiv>`);\n  }\n}\n```\n\n### Error Handling with State Management\n\n```typescript\nexport class WeatherPanel extends Panel {\n  private lastSuccessfulFetch: Date | null = null;\n\n  constructor() {\n    super({ id: 'weather', title: 'Weather Forecast' });\n    this.fetchWeather();\n  }\n\n  private async fetchWeather(): Promise\u003Cvoid> {\n    if (this.isFetching) return;\n    this.setFetching(true);\n    try {\n      const weather = await fetchWeatherData();\n      this.lastSuccessfulFetch = new Date();\n      this.renderWeather(weather);\n    } catch (err) {\n      const message = this.lastSuccessfulFetch\n        ? `Failed to update (last success: ${this.lastSuccessfulFetch.toLocaleTimeString()})`\n        : 'Failed to load initial data';\n      this.showError(message, () => this.fetchWeather());\n    } finally {\n      this.setFetching(false);\n    }\n  }\n\n  private renderWeather(weather: WeatherData): void {\n    const html = `\n      \u003Cdiv class=\"weather-current\">\n        \u003Cspan class=\"weather-temp\">${weather.temp}°\u003C\u002Fspan>\n        \u003Cspan class=\"weather-desc\">${weather.description}\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"weather-forecast\">\n        ${weather.forecast.map(day => `\n          \u003Cdiv class=\"weather-day\">\n            \u003Cspan class=\"weather-day-name\">${day.day}\u003C\u002Fspan>\n            \u003Cspan class=\"weather-day-temp\">${day.high}°\u002F${day.low}°\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n        `).join('')}\n      \u003C\u002Fdiv>\n    `;\n    this.setContent(html);\n  }\n}\n```\n",{"name":5,"description":7},"main","ff671ef61ed6587c49bd54c81cdc751c0b99e363","showcase\u002Fskills\u002Fdynamic-panel-component","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace","https:\u002F\u002Fgithub.com\u002FAG064","AG064, E.Mi.\r\n\r\nA person who thinks all the time, yet all they have to think about is nothing. I want to improve the world and work on open source projects","2026-10-08 06:53:48",{"source":8,"skillName":5,"commands":51,"prompt":61,"zipAvailable":62},[52,55,58],{"tool":53,"command":54},"npx","npx skills add AG064\u002FOpenSpace --skill dynamic-panel-component",{"tool":56,"command":57},"bunx","bunx skills add AG064\u002FOpenSpace --skill dynamic-panel-component",{"tool":59,"command":60},"pnpm","pnpm dlx skills add AG064\u002FOpenSpace --skill dynamic-panel-component","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fdynamic-panel-component\u002FSKILL.md",true,[64],{"path":65,"resourceType":66,"resourceTypeDesc":67,"sizeBytes":68,"sourceHtmlUrl":69},".skill_id","OTHER","其他",45,"https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fdynamic-panel-component\u002F.skill_id",[71,94,110,119,146,161],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":38},"01M6KKMPWA0B5SCCH766F8M5EA","write-special-chars-ts-check","Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, plus a reliable TypeScript type-checking step that filters output to only the newly-written files to avoid being misled by pre-existing errors.","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fwrite-special-chars-ts-check\u002FSKILL.md",[],[78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,29],"write special chars ts check","check","write","filters","Existing","type-checking","for","errors","script","output","type","files","only","template","file","python",{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":100,"publishedAt":38},"01M6KKMJXVBNVEY3TRK055F4A7","write-special-chars-readback","Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) using a Python heredoc script, with mandatory post-write verification via read_file and cleanup of \u002Ftmp helper scripts.","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fwrite-special-chars-readback\u002FSKILL.md",[],[101,102,80,91,103,104,92,93,105,106,107,108,109,84,86,27],"readback","write special chars readback","cleanup","read","scripts","verification","via","post","helper",{"skillNo":111,"name":112,"nameZh":6,"description":113,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":114,"categories":115,"keywords":116,"publishedAt":38},"01M6KKMEGX9M9NC105J0W3Q5B9","write-special-chars-heredoc","Workaround for write_file failures caused by special characters (apostrophes, backticks, template literals) by using a Python heredoc script executed via shell.","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fwrite-special-chars-heredoc\u002FSKILL.md",[],[80,117,93,84,107,91,118,92,86],"write special chars heredoc","shell",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":130,"publishedAt":38},"01M6KKM8EJ2AATVHZBNB916WT0","worldmonitor-reference","Curated architecture index of the WorldMonitor open-source dashboard. Reference for extracting patterns — vanilla TypeScript, Panel class hierarchy, service layer, CSS grid, API edge functions, refresh scheduler, and 60+ data source integrations.","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fworldmonitor-reference\u002FSKILL.md",[125],{"categoryNo":126,"categoryName":127,"taxonomyType":128,"taxonomyTypeDesc":129},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[131,132,133,37,29,134,135,136,137,138,84,139,21,140,141,142,143,31,144,145],"reference","worldmonitor","worldmonitor reference","css","architecture","extracting","hierarchy","source","layer","patterns","edge functions","integrations","open","data","scheduler",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":38},"01M6KKM39PHYK627F19GE2B9XN","virtual-list","Implement efficient virtual scrolling for rendering large lists with DOM recycling, chunk-based rendering, and performance optimizations","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fvirtual-list\u002FSKILL.md",[],[153,154,155,156,84,24,27,157,158,159,160],"virtual list","list","virtual","chunk","rendering","recycling","implement","performance",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":9,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":167,"publishedAt":38},"01M6KKKY7T2P61Y0JH54CDEJKQ","verify-directory-structure","Diagnose and resolve file access issues by first verifying the directory structure.","https:\u002F\u002Fgithub.com\u002FAG064\u002FOpenSpace\u002Fblob\u002Fmain\u002Fshowcase\u002Fskills\u002Fverify-directory-structure\u002FSKILL.md",[],[168,169,170,171,172,173,92,174,175,176],"directory","verify","structure","verify directory structure","access","first","issues","diagnose","file access"]