lightfriend-add-frontend-page
- Repo stars 70
- License AGPL-3.0
- Author updated Live
- Author repo lightfriend
- Domain
- Other
- Compatible agents
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- Trust score
- 94 / 100 · audit passed
- Author / version / license
- @ahtavarasmus · AGPL-3.0
- Token usage
- Lean
- Setup complexity
- Guided setup
- External API key
- Not required
- Operating systems
- Unspecified (assume cross-platform)
- Runtime requirements
- No special requirements
- Permissions
-
- Read-only
- Write / modify
- Shell exec
- Network behavior
- External requests
- Install commands
- 26 variants
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: lightfriend-add-frontend-page
description: Step-by-step guide for adding new pages to the Yew frontend This skill guides you through adding…
category: other
runtime: no special runtime
---
# lightfriend-add-frontend-page output preview
## PART A: Task fit
- Use case: Step-by-step guide for adding new pages to the Yew frontend This skill guides you through adding a new page to the Lightfriend Yew WebAssembly frontend. A complete page includes: Create frontend/src/pages/{page_name}.rs: makes outbound network calls. Works with Claude Code, Cursor, Cline and 23 more..
- Inputs: target material, constraints, expected output, and acceptance criteria.
- Evidence boundary: follow “Overview / Step-by-Step Process / 1. Create Page Component” and do not present inference as author intent.
## PART B: Execution result
- **01** The card summarizes the use case; runtime output centers on “Step-by-step guide for adding new pages to the Yew frontend This skill guides you through adding a new page to the Lightfriend Yew WebAssembly frontend. A complete page includes: Create frontend/src/pages/{page_name}.rs: makes outbound network calls. Works with Claude Code, Cursor, Cline and 23 more.”.
- **02** When the source has headings, the agent prioritizes “Overview / Step-by-Step Process / 1. Create Page Component” so the result follows the author’s structure.
- **03** Typical output includes task judgment, concrete steps, required commands or file edits, validation, and follow-up options.
- **04** Risk context follows the fingerprint: read files, write/modify files, run shell commands; may access external network resources; usually needs no extra API key.
## Running Rules
- read files, write/modify files, run shell commands; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding scope.
- Return the result, validation criteria, and next iteration options. The source does not require a stable slash command. After installation, invoke the skill by name and describe the task.
Name target files or source material, expected output, forbidden changes, and whether network or shell access is allowed. Permission fingerprint: read files, write/modify files, run shell commands.
Start with a small task and check whether the result follows “Overview / Step-by-Step Process / 1. Create Page Component”. Inspect diffs, logs, previews, or tests before expanding scope.
Confirm the final output includes a concrete result, evidence, and next action. If it stays generic, tighten inputs, boundaries, and acceptance criteria.
---
name: lightfriend-add-frontend-page
description: Step-by-step guide for adding new pages to the Yew frontend This skill guides you through adding…
category: other
source: ahtavarasmus/lightfriend
---
# lightfriend-add-frontend-page
## When to use
- Step-by-step guide for adding new pages to the Yew frontend This skill guides you through adding a new page to the Lig…
- Use it when the task has clear inputs, repeatable steps, and validation criteria.
## What to provide
- Target material, scope, expected result, and forbidden changes.
- Whether network, commands, file writes, or external services are allowed.
## Execution rules
- Organize steps around “Overview / Step-by-Step Process / 1. Create Page Component” and keep inference separate from source facts.
- read files, write/modify files, run shell commands; may access external network resources; usually needs no extra API key.
- Validate with a small sample before expanding the task.
## Output requirements
- Return the deliverable, key evidence, validation method, and next action.
- Mark missing information as unknown; do not invent commands, platforms, or dependencies. The author source anchors workflow facts; repository files anchor sources and commands; Fluxly only adds fit, limitations, and quality judgment.
skill "lightfriend-add-frontend-page" {
input -> user goal + target files + boundaries + acceptance criteria
context -> Overview / Step-by-Step Process / 1. Create Page Component
rules -> SKILL.md triggers / order / output contract
runtime -> no special runtime | read files, write/modify files, run shell commands | may access external network resources
guardrails -> usually needs no extra API key + small-sample validation + diff/log review
output -> copyable result + checklist + next iteration
} Adding a New Frontend Page
This skill guides you through adding a new page to the Lightfriend Yew WebAssembly frontend.
Overview
A complete page includes:
- Page component in
frontend/src/pages/ - Route enum variant in
main.rs - Route handler in switch function
- Navigation link (if applicable)
Step-by-Step Process
1. Create Page Component
Create frontend/src/pages/{page_name}.rs:
use yew::prelude::*;
use gloo_net::http::Request;
use crate::config;
#[function_component(PageName)]
pub fn page_name() -> Html {
// State management
let data = use_state(|| None::<SomeData>);
let loading = use_state(|| true);
let error = use_state(|| None::<String>);
// Load data on mount
{
let data = data.clone();
let loading = loading.clone();
let error = error.clone();
use_effect_with((), move |_| {
wasm_bindgen_futures::spawn_local(async move {
match fetch_data().await {
Ok(result) => {
data.set(Some(result));
loading.set(false);
}
Err(e) => {
error.set(Some(e.to_string()));
loading.set(false);
}
}
});
});
}
html! {
<div class="page-container">
<h1>{"Page Title"}</h1>
if *loading {
<p>{"Loading..."}</p>
} else if let Some(err) = (*error).clone() {
<p class="error">{err}</p>
} else if let Some(content) = (*data).clone() {
// Render content
<div>
{format!("Content: {:?}", content)}
</div>
}
</div>
}
}
// Helper functions
async fn fetch_data() -> Result<SomeData, Box<dyn std::error::Error>> {
let token = /* get from context or local storage */;
let backend_url = config::get_backend_url();
let response = Request::get(&format!("{}/api/endpoint", backend_url))
.header("Authorization", &format!("Bearer {}", token))
.send()
.await?
.json::<SomeData>()
.await?;
Ok(response)
}
#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct SomeData {
// Define your data structure
}
2. Add Module Declaration
CRITICAL: This codebase does NOT use mod.rs files!
Instead, add the module declaration to the inline mod pages { } block in frontend/src/main.rs:
mod pages {
pub mod home;
pub mod landing;
pub mod {page_name}; // Add your new page here
// ... other pages
}
NEVER create a mod.rs file - this is a common mistake. Lightfriend uses named module files (e.g., home.rs, landing.rs) and declares them in the inline module block in main.rs.
3. Add Route Variant
In frontend/src/main.rs, add a route variant to the Route enum:
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/")]
Home,
#[at("/page-name")]
PageName,
// ... other routes
#[not_found]
#[at("/404")]
NotFound,
}
4. Add Route Handler
In the switch() function in frontend/src/main.rs, add:
fn switch(route: Route) -> Html {
match route {
Route::Home => html! { <Home /> },
Route::PageName => html! { <PageName /> },
// ... other routes
Route::NotFound => html! { <h1>{"404 - Page Not Found"}</h1> },
}
}
5. Add Navigation Link (Optional)
If the page should appear in navigation, add to the Nav component in frontend/src/main.rs:
#[function_component(Nav)]
fn nav() -> Html {
html! {
<nav>
<Link<Route> to={Route::Home}>{"Home"}</Link<Route>>
<Link<Route> to={Route::PageName}>{"Page Name"}</Link<Route>>
// ... other links
</nav>
}
}
6. Test the Page
cd frontend && trunk serve
Navigate to http://localhost:8080/page-name
Common Patterns
Protected Routes (Require Auth)
use yew_hooks::use_local_storage;
#[function_component(ProtectedPage)]
pub fn protected_page() -> Html {
let token = use_local_storage::<String>("token".to_string());
if token.is_none() {
// Redirect to login
let navigator = use_navigator().unwrap();
navigator.push(&Route::Login);
return html! {};
}
html! {
<div>{"Protected content"}</div>
}
}
Page with Form
use web_sys::HtmlInputElement;
#[function_component(FormPage)]
pub fn form_page() -> Html {
let name_ref = use_node_ref();
let email_ref = use_node_ref();
let submitting = use_state(|| false);
let on_submit = {
let name_ref = name_ref.clone();
let email_ref = email_ref.clone();
let submitting = submitting.clone();
Callback::from(move |e: SubmitEvent| {
e.prevent_default();
let submitting = submitting.clone();
let name = name_ref.cast::<HtmlInputElement>()
.unwrap()
.value();
let email = email_ref.cast::<HtmlInputElement>()
.unwrap()
.value();
submitting.set(true);
wasm_bindgen_futures::spawn_local(async move {
match submit_form(name, email).await {
Ok(_) => {
// Handle success
}
Err(e) => {
// Handle error
}
}
submitting.set(false);
});
})
};
html! {
<form onsubmit={on_submit}>
<input
ref={name_ref}
type="text"
placeholder="Name"
required=true
/>
<input
ref={email_ref}
type="email"
placeholder="Email"
required=true
/>
<button type="submit" disabled={*submitting}>
if *submitting {
{"Submitting..."}
} else {
{"Submit"}
}
</button>
</form>
}
}
async fn submit_form(name: String, email: String) -> Result<(), Box<dyn std::error::Error>> {
let token = /* get from context */;
Request::post(&format!("{}/api/submit", config::get_backend_url()))
.header("Authorization", &format!("Bearer {}", token))
.json(&serde_json::json!({
"name": name,
"email": email,
}))?
.send()
.await?;
Ok(())
}
Page with Context
use yew::prelude::*;
#[derive(Clone, PartialEq)]
pub struct UserContext {
pub user_id: i32,
pub email: String,
}
#[function_component(ContextPage)]
pub fn context_page() -> Html {
let user_ctx = use_context::<UserContext>()
.expect("UserContext not found");
html! {
<div>
<p>{format!("User ID: {}", user_ctx.user_id)}</p>
<p>{format!("Email: {}", user_ctx.email)}</p>
</div>
}
}
Page with Route Parameters
#[derive(Clone, Routable, PartialEq)]
pub enum Route {
#[at("/users/:id")]
UserDetail { id: i32 },
}
#[derive(Properties, PartialEq)]
pub struct UserDetailProps {
pub id: i32,
}
#[function_component(UserDetail)]
pub fn user_detail(props: &UserDetailProps) -> Html {
let user_data = use_state(|| None::<User>);
{
let user_data = user_data.clone();
let user_id = props.id;
use_effect_with(user_id, move |_| {
wasm_bindgen_futures::spawn_local(async move {
if let Ok(user) = fetch_user(user_id).await {
user_data.set(Some(user));
}
});
});
}
html! {
<div>
if let Some(user) = (*user_data).clone() {
<h1>{user.name}</h1>
}
</div>
}
}
// In switch function:
fn switch(route: Route) -> Html {
match route {
Route::UserDetail { id } => html! { <UserDetail id={id} /> },
// ...
}
}
Page with Multiple API Calls
#[function_component(DashboardPage)]
pub fn dashboard_page() -> Html {
let stats = use_state(|| None::<Stats>);
let activity = use_state(|| None::<Vec<Activity>>);
let loading = use_state(|| true);
{
let stats = stats.clone();
let activity = activity.clone();
let loading = loading.clone();
use_effect_with((), move |_| {
wasm_bindgen_futures::spawn_local(async move {
// Fetch multiple endpoints in parallel
let (stats_result, activity_result) = tokio::join!(
fetch_stats(),
fetch_activity()
);
if let Ok(s) = stats_result {
stats.set(Some(s));
}
if let Ok(a) = activity_result {
activity.set(Some(a));
}
loading.set(false);
});
});
}
html! {
<div>
if *loading {
<p>{"Loading dashboard..."}</p>
} else {
<div>
{render_stats(&stats)}
{render_activity(&activity)}
</div>
}
</div>
}
}
Styling
Lightfriend uses CSS style blocks within the html! macro, NOT inline Tailwind classes.
Common pattern:
html! {
<div class="page-container">
<h1 class="page-title">{"Title"}</h1>
<div class="content-grid">
<div class="card">
{"Card content"}
</div>
</div>
<style>
{r#"
.page-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 1rem;
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 1rem;
}
"#}
</style>
</div>
}
Testing Checklist
- Page renders without errors
- Route works in browser
- Navigation link works (if added)
- API calls succeed
- Loading states display correctly
- Error states display correctly
- Authentication checks work (if protected)
- Mobile responsive (if applicable)
File Reference
- Page components:
frontend/src/pages/{page}.rs - Routes:
frontend/src/main.rs(Route enum + switch function) - Navigation:
frontend/src/main.rs(Nav component) - Shared components:
frontend/src/components/ - Backend config:
frontend/src/config.rs
Decide Fit First
Design Intent
How To Use It
Boundaries And Review