Spaces:
Sleeping
Complete Cybersecurity -> MuscleGrowthAI theme swap and fix Profile
Browse filesRetheme the surfaces the config swap couldn't reach and fix the
broken Profile flow so the panel is identical to the base app except
for the fitness theme.
Config-driven wiring:
- Add app.logo_icon to backend AppConfig; use it in AppHeader/Login/Signup.
- Read app.footer_text (author line) in CopyrightNotice; drop the Neon.ai
brand + patents link when a panel supplies its own footer text.
Frontend theme content (cyber/PhD -> fitness):
- User Guide, ProfileWalkthrough labels, ClearDataModal descriptions.
- Canvas subsystem: seed data/insights/presets, Training Roadmap +
Fitness Resources widgets, Documents templates, critic widgets and
sample reviews, welcome tour, onboarding tour defaults.
Backend prompts:
- Neutralize academic "student/dissertation" framing in the live
orchestrator document prompts and reference-detection regexes.
Profile fixes:
- Render ProfileWalkthrough/AccountModal/ClearDataModal in ChatPage.
- Pass correct avatarOptions/currentAvatarId props to UserAvatarPicker.
- Rename theme-coupled localStorage/tour keys to the MuscleGrowth theme.
Co-authored-by: Cursor <cursoragent@cursor.com>
- multi_llm_chatbot_backend/app/api/routes/user_profile.py +15 -14
- multi_llm_chatbot_backend/app/config.py +1 -0
- multi_llm_chatbot_backend/app/core/db.py +2 -2
- multi_llm_chatbot_backend/app/core/improved_orchestrator.py +12 -10
- multi_llm_chatbot_backend/app/core/onboarding_agent.py +27 -24
- multi_llm_chatbot_backend/app/tools/current_datetime.py +1 -1
- phd-advisor-frontend/src/components/AppHeader.js +2 -1
- phd-advisor-frontend/src/components/ClearDataModal.js +2 -2
- phd-advisor-frontend/src/components/CopyrightNotice.js +14 -1
- phd-advisor-frontend/src/components/Login.js +4 -3
- phd-advisor-frontend/src/components/OnboardingTour.js +3 -3
- phd-advisor-frontend/src/components/ProfileWalkthrough.js +15 -14
- phd-advisor-frontend/src/components/Sidebar.js +2 -2
- phd-advisor-frontend/src/components/Signup.js +12 -11
- phd-advisor-frontend/src/components/canvas/CanvasCriticWidgets.js +32 -32
- phd-advisor-frontend/src/components/canvas/CanvasDeliverables.js +103 -103
- phd-advisor-frontend/src/components/canvas/CanvasLatexEditor.js +1 -1
- phd-advisor-frontend/src/components/canvas/CanvasModals.js +1 -1
- phd-advisor-frontend/src/components/canvas/CanvasWelcomeTour.js +5 -5
- phd-advisor-frontend/src/components/canvas/CanvasWidgets.js +36 -36
- phd-advisor-frontend/src/components/canvas/canvasData.js +61 -61
- phd-advisor-frontend/src/data/userGuide.js +31 -31
- phd-advisor-frontend/src/pages/ChatPage.js +27 -1
|
@@ -30,24 +30,25 @@ PROFILE_FIELDS = [
|
|
| 30 |
LIST_FIELDS = {"primary_domains", "certifications", "tools_stack"}
|
| 31 |
|
| 32 |
_SELECT_OPTIONS: Dict[str, List[str]] = {
|
|
|
|
|
|
|
| 33 |
"cyber_role": [
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"
|
| 41 |
"Other",
|
| 42 |
],
|
| 43 |
"organization_type": [
|
| 44 |
-
"
|
| 45 |
-
"
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
-
"Independent / job seeker",
|
| 51 |
],
|
| 52 |
}
|
| 53 |
|
|
|
|
| 30 |
LIST_FIELDS = {"primary_domains", "certifications", "tools_stack"}
|
| 31 |
|
| 32 |
_SELECT_OPTIONS: Dict[str, List[str]] = {
|
| 33 |
+
# NOTE: field keys are kept generic (cyber_role / organization_type) for
|
| 34 |
+
# storage compatibility; the labels/options are themed for the panel.
|
| 35 |
"cyber_role": [
|
| 36 |
+
"Beginner lifter",
|
| 37 |
+
"General fitness",
|
| 38 |
+
"Bodybuilding / hypertrophy",
|
| 39 |
+
"Powerlifting / strength",
|
| 40 |
+
"Physique / cutting",
|
| 41 |
+
"Athlete / sport-specific",
|
| 42 |
+
"Home-gym / minimal equipment",
|
| 43 |
"Other",
|
| 44 |
],
|
| 45 |
"organization_type": [
|
| 46 |
+
"Commercial gym",
|
| 47 |
+
"Home gym",
|
| 48 |
+
"University / campus gym",
|
| 49 |
+
"CrossFit box",
|
| 50 |
+
"Outdoor / calisthenics",
|
| 51 |
+
"Hybrid (home + gym)",
|
|
|
|
| 52 |
],
|
| 53 |
}
|
| 54 |
|
|
@@ -59,6 +59,7 @@ class AppConfig(BaseModel):
|
|
| 59 |
title: str = "Advisor Canvas"
|
| 60 |
subtitle: str = "AI-Powered Guidance"
|
| 61 |
primary_color: str = "#7C3AED"
|
|
|
|
| 62 |
footer_text: str = ""
|
| 63 |
user_avatars: List[UserAvatarOption] = []
|
| 64 |
|
|
|
|
| 59 |
title: str = "Advisor Canvas"
|
| 60 |
subtitle: str = "AI-Powered Guidance"
|
| 61 |
primary_color: str = "#7C3AED"
|
| 62 |
+
logo_icon: str = "Users"
|
| 63 |
footer_text: str = ""
|
| 64 |
user_avatars: List[UserAvatarOption] = []
|
| 65 |
|
|
@@ -10,9 +10,9 @@ party). The replacement is a single Hugging Face Storage Bucket mounted into
|
|
| 10 |
the Space at ``$DATA_DIR`` (default ``/data``). Everything durable lives
|
| 11 |
there:
|
| 12 |
|
| 13 |
-
${DATA_DIR}/
|
| 14 |
sessions, profiles, onboarding,
|
| 15 |
-
|
| 16 |
|
| 17 |
A single ``hf buckets sync hf://buckets/<org>/<bucket> ./backup`` therefore
|
| 18 |
captures the full state and can be restored before the next redeploy.
|
|
|
|
| 10 |
the Space at ``$DATA_DIR`` (default ``/data``). Everything durable lives
|
| 11 |
there:
|
| 12 |
|
| 13 |
+
${DATA_DIR}/muscle_growth_panel.db — SQLite database (users, chat
|
| 14 |
sessions, profiles, onboarding,
|
| 15 |
+
canvases)
|
| 16 |
|
| 17 |
A single ``hf buckets sync hf://buckets/<org>/<bucket> ./backup`` therefore
|
| 18 |
captures the full state and can be restored before the next redeploy.
|
|
@@ -61,11 +61,13 @@ class ImprovedChatOrchestrator:
|
|
| 61 |
if not tool_definitions:
|
| 62 |
return ToolCallResult(text="", used_tool=False)
|
| 63 |
|
|
|
|
| 64 |
system_prompt = (
|
| 65 |
-
"You are a helpful
|
|
|
|
| 66 |
"Use the available tools when the user's question can be answered by one of them. "
|
| 67 |
"Call get_current_datetime when the user asks about today, deadlines, timelines, "
|
| 68 |
-
"schedules,
|
| 69 |
"your guidance — then weave the result into your answer. "
|
| 70 |
"If no tool is relevant, respond with a brief text answer. "
|
| 71 |
"Format your responses using markdown."
|
|
@@ -171,8 +173,8 @@ class ImprovedChatOrchestrator:
|
|
| 171 |
# Common document reference patterns
|
| 172 |
patterns = [
|
| 173 |
r"(?:my|the|in)\s+([a-zA-Z_\-]+\.(?:pdf|docx|txt))", # specific files
|
| 174 |
-
r"(?:my|the)\s+(
|
| 175 |
-
r"(?:in|from)\s+(?:my\s+)?([a-zA-Z_\-\s]+(?:
|
| 176 |
]
|
| 177 |
|
| 178 |
for pattern in patterns:
|
|
@@ -557,8 +559,8 @@ class ImprovedChatOrchestrator:
|
|
| 557 |
# Common patterns for document references
|
| 558 |
document_indicators = [
|
| 559 |
r"(?:my|the|in|from)\s+([a-zA-Z_\-]+\.(?:pdf|docx|txt|doc))", # specific files
|
| 560 |
-
r"(?:my|the)\s+(
|
| 561 |
-
r"(?:in|from)\s+(?:my\s+)?([a-zA-Z_\-\s]+(?:
|
| 562 |
r"(?:the|my)\s+([a-zA-Z_\-\s]+(?:document|file))", # generic documents
|
| 563 |
]
|
| 564 |
|
|
@@ -706,12 +708,12 @@ When analyzing the document context:
|
|
| 706 |
system_message = f"""{persona.system_prompt}
|
| 707 |
|
| 708 |
CURRENT SESSION CONTEXT:
|
| 709 |
-
The
|
| 710 |
|
| 711 |
DOCUMENT CONTENT:
|
| 712 |
{document_context}
|
| 713 |
|
| 714 |
-
IMPORTANT: When the
|
| 715 |
|
| 716 |
Always cite your sources when referencing information from their documents using the format: "According to your [document_name]..." or "In your [section_name] from [document_name]..."
|
| 717 |
"""
|
|
@@ -719,9 +721,9 @@ When analyzing the document context:
|
|
| 719 |
# NO DOCUMENTS - Explicitly tell persona not to reference documents
|
| 720 |
system_message = f"""{persona.system_prompt}
|
| 721 |
|
| 722 |
-
IMPORTANT: The
|
| 723 |
|
| 724 |
-
If they mention "my document," "my
|
| 725 |
1. Acknowledge that you don't have access to their specific documents
|
| 726 |
2. Ask them to upload the relevant files for more targeted advice
|
| 727 |
3. Provide general guidance based on best practices in your area of expertise
|
|
|
|
| 61 |
if not tool_definitions:
|
| 62 |
return ToolCallResult(text="", used_tool=False)
|
| 63 |
|
| 64 |
+
app_cfg = settings.app
|
| 65 |
system_prompt = (
|
| 66 |
+
f"You are a helpful assistant for {app_cfg.title} — {app_cfg.subtitle}. "
|
| 67 |
+
"You have access to external tools. "
|
| 68 |
"Use the available tools when the user's question can be answered by one of them. "
|
| 69 |
"Call get_current_datetime when the user asks about today, deadlines, timelines, "
|
| 70 |
+
"schedules, or when accurate date/time context would improve "
|
| 71 |
"your guidance — then weave the result into your answer. "
|
| 72 |
"If no tool is relevant, respond with a brief text answer. "
|
| 73 |
"Format your responses using markdown."
|
|
|
|
| 173 |
# Common document reference patterns
|
| 174 |
patterns = [
|
| 175 |
r"(?:my|the|in)\s+([a-zA-Z_\-]+\.(?:pdf|docx|txt))", # specific files
|
| 176 |
+
r"(?:my|the)\s+(program|plan|routine|split|log|protocol|document|proposal|chapter|report)", # document types
|
| 177 |
+
r"(?:in|from)\s+(?:my\s+)?([a-zA-Z_\-\s]+(?:section|plan))", # sections
|
| 178 |
]
|
| 179 |
|
| 180 |
for pattern in patterns:
|
|
|
|
| 559 |
# Common patterns for document references
|
| 560 |
document_indicators = [
|
| 561 |
r"(?:my|the|in|from)\s+([a-zA-Z_\-]+\.(?:pdf|docx|txt|doc))", # specific files
|
| 562 |
+
r"(?:my|the)\s+(program|plan|routine|split|log|protocol|document|proposal|chapter|report)", # document types
|
| 563 |
+
r"(?:in|from)\s+(?:my\s+)?([a-zA-Z_\-\s]+(?:section|plan|program))", # sections
|
| 564 |
r"(?:the|my)\s+([a-zA-Z_\-\s]+(?:document|file))", # generic documents
|
| 565 |
]
|
| 566 |
|
|
|
|
| 708 |
system_message = f"""{persona.system_prompt}
|
| 709 |
|
| 710 |
CURRENT SESSION CONTEXT:
|
| 711 |
+
The user has uploaded the following documents: {doc_list}
|
| 712 |
|
| 713 |
DOCUMENT CONTENT:
|
| 714 |
{document_context}
|
| 715 |
|
| 716 |
+
IMPORTANT: When the user refers to "my document," "my plan," "my program," etc., they are referring to one of their uploaded documents. Use the document context above to understand which specific document they mean and reference it by name in your response.
|
| 717 |
|
| 718 |
Always cite your sources when referencing information from their documents using the format: "According to your [document_name]..." or "In your [section_name] from [document_name]..."
|
| 719 |
"""
|
|
|
|
| 721 |
# NO DOCUMENTS - Explicitly tell persona not to reference documents
|
| 722 |
system_message = f"""{persona.system_prompt}
|
| 723 |
|
| 724 |
+
IMPORTANT: The user has NOT uploaded any documents yet. Do not reference any specific documents, files, or assume you have access to their materials.
|
| 725 |
|
| 726 |
+
If they mention "my document," "my plan," "my program," etc., you should:
|
| 727 |
1. Acknowledge that you don't have access to their specific documents
|
| 728 |
2. Ask them to upload the relevant files for more targeted advice
|
| 729 |
3. Provide general guidance based on best practices in your area of expertise
|
|
@@ -1,5 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
-
OnboardingAgent — conversational profile gathering for
|
| 3 |
"""
|
| 4 |
|
| 5 |
import json
|
|
@@ -12,27 +12,30 @@ from app.core.database import get_database
|
|
| 12 |
|
| 13 |
LOG = logging.getLogger(__name__)
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
PROFILE_FIELDS: List[tuple] = [
|
| 16 |
-
("knowledge_level", "What is your
|
| 17 |
-
"Level such as
|
| 18 |
("timezone", "What time zone are you usually in?",
|
| 19 |
"IANA timezone or region such as America/New_York, Europe/London, UTC"),
|
| 20 |
-
("cyber_role", "What best describes your
|
| 21 |
-
"
|
| 22 |
-
("organization_type", "
|
| 23 |
-
"
|
| 24 |
-
("primary_domains", "Which
|
| 25 |
-
"Comma-separated areas such as
|
| 26 |
-
("certifications", "
|
| 27 |
-
"List such as
|
| 28 |
-
("tools_stack", "What
|
| 29 |
-
"
|
| 30 |
-
("compliance_focus", "Any
|
| 31 |
-
"
|
| 32 |
("current_goals", "What are you trying to accomplish in the next few months?",
|
| 33 |
-
"
|
| 34 |
-
("learning_preferences", "How do you prefer to
|
| 35 |
-
"
|
| 36 |
]
|
| 37 |
|
| 38 |
|
|
@@ -92,8 +95,8 @@ class OnboardingAgent:
|
|
| 92 |
|
| 93 |
if not missing:
|
| 94 |
return {
|
| 95 |
-
"reply": "Great — your
|
| 96 |
-
"
|
| 97 |
"progress": 100,
|
| 98 |
"complete": True,
|
| 99 |
}
|
|
@@ -115,7 +118,7 @@ class OnboardingAgent:
|
|
| 115 |
)
|
| 116 |
field_descriptions = "\n".join(f'- "{k}": {desc}' for k, _q, desc in missing_fields)
|
| 117 |
system = (
|
| 118 |
-
"Extract
|
| 119 |
"Return ONLY valid JSON with field names as keys. "
|
| 120 |
"For list fields return a JSON array. "
|
| 121 |
f"{skip_instruction}\n"
|
|
@@ -149,8 +152,8 @@ class OnboardingAgent:
|
|
| 149 |
filled_summary = ", ".join(filled_parts) or "nothing yet"
|
| 150 |
next_field_key, next_field_q, _ = missing[0]
|
| 151 |
system = (
|
| 152 |
-
"You are a friendly
|
| 153 |
-
"You help users build a profile so AI
|
| 154 |
"RULES:\n"
|
| 155 |
"- Respond in exactly ONE short paragraph (2-3 sentences).\n"
|
| 156 |
"- Briefly acknowledge what they said, then ask ONE clear question.\n"
|
|
@@ -177,4 +180,4 @@ class OnboardingAgent:
|
|
| 177 |
return reply
|
| 178 |
except Exception as e:
|
| 179 |
LOG.error(f"Question generation failed: {e}")
|
| 180 |
-
return missing[0][1] if missing else "Tell me more about your
|
|
|
|
| 1 |
"""
|
| 2 |
+
OnboardingAgent — conversational profile gathering for fitness advisors.
|
| 3 |
"""
|
| 4 |
|
| 5 |
import json
|
|
|
|
| 12 |
|
| 13 |
LOG = logging.getLogger(__name__)
|
| 14 |
|
| 15 |
+
# NOTE: field keys are kept stable (knowledge_level, cyber_role, ...) for storage
|
| 16 |
+
# compatibility with the user profile schema; the questions/descriptions below
|
| 17 |
+
# are themed for a fitness / bodybuilding advisor panel.
|
| 18 |
PROFILE_FIELDS: List[tuple] = [
|
| 19 |
+
("knowledge_level", "What is your fitness experience level?",
|
| 20 |
+
"Level such as beginner, intermediate, or advanced"),
|
| 21 |
("timezone", "What time zone are you usually in?",
|
| 22 |
"IANA timezone or region such as America/New_York, Europe/London, UTC"),
|
| 23 |
+
("cyber_role", "What best describes your training focus right now?",
|
| 24 |
+
"Focus such as general fitness, bodybuilding/hypertrophy, powerlifting/strength, cutting, or athlete"),
|
| 25 |
+
("organization_type", "Where do you usually train?",
|
| 26 |
+
"Commercial gym, home gym, campus gym, CrossFit box, outdoor/calisthenics, or hybrid"),
|
| 27 |
+
("primary_domains", "Which muscle groups or areas do you focus on most?",
|
| 28 |
+
"Comma-separated areas such as chest, back, legs, arms, core, conditioning"),
|
| 29 |
+
("certifications", "What equipment do you have access to?",
|
| 30 |
+
"List such as dumbbells, barbell, cables, bands, machines, or bodyweight only"),
|
| 31 |
+
("tools_stack", "What apps or trackers do you use regularly?",
|
| 32 |
+
"MyFitnessPal, Strong, Hevy, Apple Health, a spreadsheet, etc."),
|
| 33 |
+
("compliance_focus", "Any dietary approach or restrictions we should know about?",
|
| 34 |
+
"High-protein, vegetarian/vegan, cutting, bulking, allergies, or none"),
|
| 35 |
("current_goals", "What are you trying to accomplish in the next few months?",
|
| 36 |
+
"Muscle gain, fat loss, strength PRs, first pull-up, recomposition, etc."),
|
| 37 |
+
("learning_preferences", "How do you prefer to train?",
|
| 38 |
+
"Full-body, upper/lower or bro splits, supersets, progressive overload, etc."),
|
| 39 |
]
|
| 40 |
|
| 41 |
|
|
|
|
| 95 |
|
| 96 |
if not missing:
|
| 97 |
return {
|
| 98 |
+
"reply": "Great — your fitness profile is complete. Your coaches will tailor workouts, "
|
| 99 |
+
"nutrition, and next steps to your goals, equipment, and experience.",
|
| 100 |
"progress": 100,
|
| 101 |
"complete": True,
|
| 102 |
}
|
|
|
|
| 118 |
)
|
| 119 |
field_descriptions = "\n".join(f'- "{k}": {desc}' for k, _q, desc in missing_fields)
|
| 120 |
system = (
|
| 121 |
+
"Extract fitness profile fields from the user's message. "
|
| 122 |
"Return ONLY valid JSON with field names as keys. "
|
| 123 |
"For list fields return a JSON array. "
|
| 124 |
f"{skip_instruction}\n"
|
|
|
|
| 152 |
filled_summary = ", ".join(filled_parts) or "nothing yet"
|
| 153 |
next_field_key, next_field_q, _ = missing[0]
|
| 154 |
system = (
|
| 155 |
+
"You are a friendly fitness onboarding assistant. "
|
| 156 |
+
"You help users build a profile so AI fitness coaches can personalize workouts and nutrition.\n"
|
| 157 |
"RULES:\n"
|
| 158 |
"- Respond in exactly ONE short paragraph (2-3 sentences).\n"
|
| 159 |
"- Briefly acknowledge what they said, then ask ONE clear question.\n"
|
|
|
|
| 180 |
return reply
|
| 181 |
except Exception as e:
|
| 182 |
LOG.error(f"Question generation failed: {e}")
|
| 183 |
+
return missing[0][1] if missing else "Tell me more about your fitness background!"
|
|
@@ -17,7 +17,7 @@ TOOL_DEFINITION = {
|
|
| 17 |
"description": (
|
| 18 |
"Get the current date and time in UTC and in a configured local timezone. "
|
| 19 |
"Use when the user asks about today, deadlines, timelines, schedules, "
|
| 20 |
-
"or when accurate temporal context improves
|
| 21 |
),
|
| 22 |
"parameters": {
|
| 23 |
"type": "object",
|
|
|
|
| 17 |
"description": (
|
| 18 |
"Get the current date and time in UTC and in a configured local timezone. "
|
| 19 |
"Use when the user asks about today, deadlines, timelines, schedules, "
|
| 20 |
+
"or when accurate temporal context improves the guidance."
|
| 21 |
),
|
| 22 |
"parameters": {
|
| 23 |
"type": "object",
|
|
@@ -22,7 +22,8 @@ const AppHeader = ({
|
|
| 22 |
children,
|
| 23 |
}) => {
|
| 24 |
const { config, resolveIcon } = useAppConfig();
|
| 25 |
-
const
|
|
|
|
| 26 |
|
| 27 |
const goToCanvas = (view) => {
|
| 28 |
if (onNavigateToCanvas) onNavigateToCanvas(view);
|
|
|
|
| 22 |
children,
|
| 23 |
}) => {
|
| 24 |
const { config, resolveIcon } = useAppConfig();
|
| 25 |
+
const logoIconName = config?.app?.logo_icon || 'Users';
|
| 26 |
+
const BrandIcon = resolveIcon ? resolveIcon(logoIconName) : Users;
|
| 27 |
|
| 28 |
const goToCanvas = (view) => {
|
| 29 |
if (onNavigateToCanvas) onNavigateToCanvas(view);
|
|
@@ -123,7 +123,7 @@ const ClearDataModal = ({ authToken, onClose, onDataCleared }) => {
|
|
| 123 |
<div>
|
| 124 |
<div style={{ fontWeight: 600, fontSize: 14 }}>Profile Information</div>
|
| 125 |
<div style={{ fontSize: 12, color: isDark ? '#9ca3af' : '#6b7280', marginTop: 2 }}>
|
| 126 |
-
|
| 127 |
</div>
|
| 128 |
</div>
|
| 129 |
</div>
|
|
@@ -143,7 +143,7 @@ const ClearDataModal = ({ authToken, onClose, onDataCleared }) => {
|
|
| 143 |
<div>
|
| 144 |
<div style={{ fontWeight: 600, fontSize: 14 }}>Canvas</div>
|
| 145 |
<div style={{ fontSize: 12, color: isDark ? '#9ca3af' : '#6b7280', marginTop: 2 }}>
|
| 146 |
-
All
|
| 147 |
</div>
|
| 148 |
</div>
|
| 149 |
</div>
|
|
|
|
| 123 |
<div>
|
| 124 |
<div style={{ fontWeight: 600, fontSize: 14 }}>Profile Information</div>
|
| 125 |
<div style={{ fontSize: 12, color: isDark ? '#9ca3af' : '#6b7280', marginTop: 2 }}>
|
| 126 |
+
Fitness level, goals, training focus, equipment, etc. Resets "Tell us about yourself."
|
| 127 |
</div>
|
| 128 |
</div>
|
| 129 |
</div>
|
|
|
|
| 143 |
<div>
|
| 144 |
<div style={{ fontWeight: 600, fontSize: 14 }}>Canvas</div>
|
| 145 |
<div style={{ fontSize: 12, color: isDark ? '#9ca3af' : '#6b7280', marginTop: 2 }}>
|
| 146 |
+
All saved workout plans, progress notes, and insights.
|
| 147 |
</div>
|
| 148 |
</div>
|
| 149 |
</div>
|
|
@@ -1,11 +1,23 @@
|
|
| 1 |
import React from 'react';
|
|
|
|
| 2 |
|
| 3 |
const CopyrightNotice = ({ variant = 'footer', className = '' }) => {
|
|
|
|
| 4 |
const isSidebar = variant === 'sidebar';
|
| 5 |
const textClass = isSidebar ? 'sidebar-copyright-text' : 'footer-text';
|
| 6 |
const patentsClass = isSidebar ? 'sidebar-patents-link' : 'footer-patents-link';
|
| 7 |
const combinedClass = className ? `${textClass} ${className}` : textClass;
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
return (
|
| 10 |
<p className={combinedClass}>
|
| 11 |
{'\u00A9 '}
|
|
@@ -22,7 +34,8 @@ const CopyrightNotice = ({ variant = 'footer', className = '' }) => {
|
|
| 22 |
Neon.ai
|
| 23 |
</a>
|
| 24 |
)}
|
| 25 |
-
. All rights reserved.
|
|
|
|
| 26 |
<a
|
| 27 |
href="https://www.neon.ai/contact"
|
| 28 |
target="_blank"
|
|
|
|
| 1 |
import React from 'react';
|
| 2 |
+
import { useAppConfig } from '../contexts/AppConfigContext';
|
| 3 |
|
| 4 |
const CopyrightNotice = ({ variant = 'footer', className = '' }) => {
|
| 5 |
+
const { config } = useAppConfig();
|
| 6 |
const isSidebar = variant === 'sidebar';
|
| 7 |
const textClass = isSidebar ? 'sidebar-copyright-text' : 'footer-text';
|
| 8 |
const patentsClass = isSidebar ? 'sidebar-patents-link' : 'footer-patents-link';
|
| 9 |
const combinedClass = className ? `${textClass} ${className}` : textClass;
|
| 10 |
|
| 11 |
+
// Prefer the author/copyright line from config (app.footer_text). This keeps
|
| 12 |
+
// the footer themed per-panel instead of hardcoding a single brand.
|
| 13 |
+
const footerText = config?.app?.footer_text?.trim();
|
| 14 |
+
|
| 15 |
+
// When a panel provides its own footer_text (author/copyright line), show only
|
| 16 |
+
// that and skip the Neon.ai brand + patents link.
|
| 17 |
+
if (footerText) {
|
| 18 |
+
return <p className={combinedClass}>{footerText}</p>;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
return (
|
| 22 |
<p className={combinedClass}>
|
| 23 |
{'\u00A9 '}
|
|
|
|
| 34 |
Neon.ai
|
| 35 |
</a>
|
| 36 |
)}
|
| 37 |
+
. All rights reserved.
|
| 38 |
+
{' '}
|
| 39 |
<a
|
| 40 |
href="https://www.neon.ai/contact"
|
| 41 |
target="_blank"
|
|
@@ -1,11 +1,12 @@
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
-
import { Eye, EyeOff, Mail, Lock, ArrowRight,
|
| 3 |
import { useAppConfig } from '../contexts/AppConfigContext';
|
| 4 |
import CopyrightNotice from './CopyrightNotice';
|
| 5 |
import '../styles/Login.css';
|
| 6 |
|
| 7 |
const Login = ({ onNavigateToSignup, onNavigateToHome }) => {
|
| 8 |
-
const { config } = useAppConfig();
|
|
|
|
| 9 |
const [showPassword, setShowPassword] = useState(false);
|
| 10 |
const [formData, setFormData] = useState({
|
| 11 |
email: '',
|
|
@@ -103,7 +104,7 @@ const Login = ({ onNavigateToSignup, onNavigateToHome }) => {
|
|
| 103 |
{/* Header */}
|
| 104 |
<div className="login-header">
|
| 105 |
<div className="logo-container">
|
| 106 |
-
<
|
| 107 |
</div>
|
| 108 |
<h1 className="login-title">Welcome Back</h1>
|
| 109 |
<p className="login-subtitle">
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
+
import { Eye, EyeOff, Mail, Lock, ArrowRight, Phone } from 'lucide-react';
|
| 3 |
import { useAppConfig } from '../contexts/AppConfigContext';
|
| 4 |
import CopyrightNotice from './CopyrightNotice';
|
| 5 |
import '../styles/Login.css';
|
| 6 |
|
| 7 |
const Login = ({ onNavigateToSignup, onNavigateToHome }) => {
|
| 8 |
+
const { config, resolveIcon } = useAppConfig();
|
| 9 |
+
const LogoIcon = resolveIcon(config?.app?.logo_icon || 'BookOpen');
|
| 10 |
const [showPassword, setShowPassword] = useState(false);
|
| 11 |
const [formData, setFormData] = useState({
|
| 12 |
email: '',
|
|
|
|
| 104 |
{/* Header */}
|
| 105 |
<div className="login-header">
|
| 106 |
<div className="logo-container">
|
| 107 |
+
<LogoIcon className="logo-icon" />
|
| 108 |
</div>
|
| 109 |
<h1 className="login-title">Welcome Back</h1>
|
| 110 |
<p className="login-subtitle">
|
|
@@ -16,8 +16,8 @@ const DEFAULT_FEATURES = [
|
|
| 16 |
{ Icon: BarChart3, label: 'Track your progress on a structured canvas' },
|
| 17 |
];
|
| 18 |
const DEFAULT_CANVAS_STEP = {
|
| 19 |
-
title: '
|
| 20 |
-
body: 'A dashboard view of your
|
| 21 |
};
|
| 22 |
|
| 23 |
const buildAdvisorBody = (advisors) => {
|
|
@@ -255,7 +255,7 @@ const OnboardingTour = ({ children }) => {
|
|
| 255 |
const c = useMemo(() => palette(isDark), [isDark]);
|
| 256 |
|
| 257 |
const stepData = useMemo(() => ({
|
| 258 |
-
title: config?.app?.title || '
|
| 259 |
subtitle: config?.app?.subtitle || 'AI-Powered Guidance',
|
| 260 |
features: buildFeatures(config, resolveIcon),
|
| 261 |
advisorBody: buildAdvisorBody(advisors),
|
|
|
|
| 16 |
{ Icon: BarChart3, label: 'Track your progress on a structured canvas' },
|
| 17 |
];
|
| 18 |
const DEFAULT_CANVAS_STEP = {
|
| 19 |
+
title: 'Progress Canvas',
|
| 20 |
+
body: 'A dashboard view of your training journey — program progress, nutrition, next steps, all in one place.',
|
| 21 |
};
|
| 22 |
|
| 23 |
const buildAdvisorBody = (advisors) => {
|
|
|
|
| 255 |
const c = useMemo(() => palette(isDark), [isDark]);
|
| 256 |
|
| 257 |
const stepData = useMemo(() => ({
|
| 258 |
+
title: config?.app?.title || 'Advisor Panel',
|
| 259 |
subtitle: config?.app?.subtitle || 'AI-Powered Guidance',
|
| 260 |
features: buildFeatures(config, resolveIcon),
|
| 261 |
advisorBody: buildAdvisorBody(advisors),
|
|
@@ -17,13 +17,14 @@ const buildSteps = (config) => {
|
|
| 17 |
fields: [
|
| 18 |
{
|
| 19 |
key: 'knowledge_level',
|
| 20 |
-
label: '
|
| 21 |
type: 'select',
|
| 22 |
options: knowledgeLevels.length
|
| 23 |
? knowledgeLevels
|
| 24 |
: [
|
| 25 |
-
{ value: '
|
| 26 |
-
{ value: '
|
|
|
|
| 27 |
],
|
| 28 |
},
|
| 29 |
{
|
|
@@ -37,26 +38,26 @@ const buildSteps = (config) => {
|
|
| 37 |
],
|
| 38 |
},
|
| 39 |
{
|
| 40 |
-
title: '
|
| 41 |
fields: [
|
| 42 |
-
{ key: 'cyber_role', label: 'Your
|
| 43 |
-
{ key: 'organization_type', label: '
|
| 44 |
],
|
| 45 |
},
|
| 46 |
{
|
| 47 |
-
title: 'Focus &
|
| 48 |
fields: [
|
| 49 |
-
{ key: 'primary_domains', label: '
|
| 50 |
-
{ key: 'certifications', label: '
|
| 51 |
-
{ key: 'tools_stack', label: '
|
| 52 |
],
|
| 53 |
},
|
| 54 |
{
|
| 55 |
-
title: 'Goals &
|
| 56 |
fields: [
|
| 57 |
-
{ key: 'compliance_focus', label: '
|
| 58 |
-
{ key: 'current_goals', label: 'Current goals', type: 'textarea', placeholder: '
|
| 59 |
-
{ key: 'learning_preferences', label: 'How you
|
| 60 |
],
|
| 61 |
},
|
| 62 |
];
|
|
|
|
| 17 |
fields: [
|
| 18 |
{
|
| 19 |
key: 'knowledge_level',
|
| 20 |
+
label: 'Fitness level',
|
| 21 |
type: 'select',
|
| 22 |
options: knowledgeLevels.length
|
| 23 |
? knowledgeLevels
|
| 24 |
: [
|
| 25 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 26 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 27 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 28 |
],
|
| 29 |
},
|
| 30 |
{
|
|
|
|
| 38 |
],
|
| 39 |
},
|
| 40 |
{
|
| 41 |
+
title: 'Training focus & setup',
|
| 42 |
fields: [
|
| 43 |
+
{ key: 'cyber_role', label: 'Your training focus', type: 'select', options: ['Beginner lifter', 'General fitness', 'Bodybuilding / hypertrophy', 'Powerlifting / strength', 'Physique / cutting', 'Athlete / sport-specific', 'Home-gym / minimal equipment', 'Other'] },
|
| 44 |
+
{ key: 'organization_type', label: 'Where you train', type: 'select', options: ['Commercial gym', 'Home gym', 'University / campus gym', 'CrossFit box', 'Outdoor / calisthenics', 'Hybrid (home + gym)'] },
|
| 45 |
],
|
| 46 |
},
|
| 47 |
{
|
| 48 |
+
title: 'Focus & equipment',
|
| 49 |
fields: [
|
| 50 |
+
{ key: 'primary_domains', label: 'Focus muscle groups / areas (comma-separated)', type: 'text', placeholder: 'e.g. chest, back, legs, arms, core, conditioning' },
|
| 51 |
+
{ key: 'certifications', label: 'Available equipment (comma-separated)', type: 'text', placeholder: 'e.g. dumbbells, barbell, cables, bands, machines' },
|
| 52 |
+
{ key: 'tools_stack', label: 'Apps & trackers you use (comma-separated)', type: 'text', placeholder: 'e.g. MyFitnessPal, Strong, Hevy, Apple Health' },
|
| 53 |
],
|
| 54 |
},
|
| 55 |
{
|
| 56 |
+
title: 'Goals & nutrition',
|
| 57 |
fields: [
|
| 58 |
+
{ key: 'compliance_focus', label: 'Dietary approach / restrictions', type: 'text', placeholder: 'e.g. high-protein, vegetarian, cutting, bulking' },
|
| 59 |
+
{ key: 'current_goals', label: 'Current goals', type: 'textarea', placeholder: 'Muscle gain, fat loss, strength PRs, first pull-up...' },
|
| 60 |
+
{ key: 'learning_preferences', label: 'How you like to train', type: 'text', placeholder: 'Full-body, splits, supersets, progressive overload...' },
|
| 61 |
],
|
| 62 |
},
|
| 63 |
];
|
|
@@ -562,8 +562,8 @@ const Sidebar = ({
|
|
| 562 |
|
| 563 |
{showAvatarPicker && (
|
| 564 |
<UserAvatarPicker
|
| 565 |
-
|
| 566 |
-
|
| 567 |
onSelect={(id) => { onAvatarChange?.(id); setShowAvatarPicker(false); }}
|
| 568 |
onClose={() => setShowAvatarPicker(false)}
|
| 569 |
/>
|
|
|
|
| 562 |
|
| 563 |
{showAvatarPicker && (
|
| 564 |
<UserAvatarPicker
|
| 565 |
+
avatarOptions={avatarOptions}
|
| 566 |
+
currentAvatarId={userAvatarId}
|
| 567 |
onSelect={(id) => { onAvatarChange?.(id); setShowAvatarPicker(false); }}
|
| 568 |
onClose={() => setShowAvatarPicker(false)}
|
| 569 |
/>
|
|
@@ -4,7 +4,8 @@ import { useAppConfig } from '../contexts/AppConfigContext';
|
|
| 4 |
import '../styles/Signup.css';
|
| 5 |
|
| 6 |
const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
| 7 |
-
const { config } = useAppConfig();
|
|
|
|
| 8 |
const [showPassword, setShowPassword] = useState(false);
|
| 9 |
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
| 10 |
const [formData, setFormData] = useState({
|
|
@@ -24,12 +25,12 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
|
| 24 |
: config?.login?.academic_stages?.length
|
| 25 |
? config.login.academic_stages
|
| 26 |
: [
|
| 27 |
-
{ value: '', label: 'Select your
|
| 28 |
-
{ value: '
|
| 29 |
-
{ value: '
|
| 30 |
-
{ value: '
|
| 31 |
-
{ value: '
|
| 32 |
-
{ value: '
|
| 33 |
];
|
| 34 |
|
| 35 |
const timezones = config?.login?.timezones?.length
|
|
@@ -83,7 +84,7 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
|
| 83 |
}
|
| 84 |
|
| 85 |
if (!formData.academicStage) {
|
| 86 |
-
newErrors.academicStage = 'Please select your
|
| 87 |
}
|
| 88 |
|
| 89 |
setErrors(newErrors);
|
|
@@ -148,7 +149,7 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
|
| 148 |
{/* Header */}
|
| 149 |
<div className="signup-header">
|
| 150 |
<div className="logo-container">
|
| 151 |
-
<
|
| 152 |
</div>
|
| 153 |
<h1 className="signup-title">Join Our Community</h1>
|
| 154 |
<p className="signup-subtitle">
|
|
@@ -293,10 +294,10 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
|
| 293 |
</div>
|
| 294 |
</div>
|
| 295 |
|
| 296 |
-
{/*
|
| 297 |
<div className="form-group">
|
| 298 |
<label htmlFor="academicStage" className="form-label">
|
| 299 |
-
|
| 300 |
</label>
|
| 301 |
<div className="input-container">
|
| 302 |
<Shield className="input-icon" />
|
|
|
|
| 4 |
import '../styles/Signup.css';
|
| 5 |
|
| 6 |
const Signup = ({ onNavigateToLogin, onNavigateToHome }) => {
|
| 7 |
+
const { config, resolveIcon } = useAppConfig();
|
| 8 |
+
const LogoIcon = resolveIcon(config?.app?.logo_icon || 'Shield');
|
| 9 |
const [showPassword, setShowPassword] = useState(false);
|
| 10 |
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
| 11 |
const [formData, setFormData] = useState({
|
|
|
|
| 25 |
: config?.login?.academic_stages?.length
|
| 26 |
? config.login.academic_stages
|
| 27 |
: [
|
| 28 |
+
{ value: '', label: 'Select your fitness level' },
|
| 29 |
+
{ value: 'beginner', label: 'Beginner' },
|
| 30 |
+
{ value: 'intermediate', label: 'Intermediate' },
|
| 31 |
+
{ value: 'advanced', label: 'Advanced' },
|
| 32 |
+
{ value: 'strength_focus', label: 'Strength training' },
|
| 33 |
+
{ value: 'cardio_focus', label: 'Run & cardio' },
|
| 34 |
];
|
| 35 |
|
| 36 |
const timezones = config?.login?.timezones?.length
|
|
|
|
| 84 |
}
|
| 85 |
|
| 86 |
if (!formData.academicStage) {
|
| 87 |
+
newErrors.academicStage = 'Please select your fitness level';
|
| 88 |
}
|
| 89 |
|
| 90 |
setErrors(newErrors);
|
|
|
|
| 149 |
{/* Header */}
|
| 150 |
<div className="signup-header">
|
| 151 |
<div className="logo-container">
|
| 152 |
+
<LogoIcon className="logo-icon" />
|
| 153 |
</div>
|
| 154 |
<h1 className="signup-title">Join Our Community</h1>
|
| 155 |
<p className="signup-subtitle">
|
|
|
|
| 294 |
</div>
|
| 295 |
</div>
|
| 296 |
|
| 297 |
+
{/* Fitness level */}
|
| 298 |
<div className="form-group">
|
| 299 |
<label htmlFor="academicStage" className="form-label">
|
| 300 |
+
Fitness level
|
| 301 |
</label>
|
| 302 |
<div className="input-container">
|
| 303 |
<Shield className="input-icon" />
|
|
@@ -97,7 +97,7 @@ export function DevilsAdvocateWidget({ state, setState, openModal }) {
|
|
| 97 |
className="btn"
|
| 98 |
title="Open Devil's Advocate in the main chat (history lives there)"
|
| 99 |
onClick={() => handoffToChat("Devil's Advocate",
|
| 100 |
-
`Take the position of devil's advocate on my claim: "${state.claim || 'my current
|
| 101 |
>
|
| 102 |
<Icon name="message" size={13}/>Open in chat
|
| 103 |
</button>
|
|
@@ -142,7 +142,7 @@ export function ScopeRealismWidget({ state, openModal }) {
|
|
| 142 |
className="btn"
|
| 143 |
title="Open Scope Realism in the main chat (history lives there)"
|
| 144 |
onClick={() => handoffToChat('Scope Realism',
|
| 145 |
-
`Run a brutal feasibility check on my goal: "${state.target || 'my current
|
| 146 |
>
|
| 147 |
<Icon name="message" size={13}/>Open in chat
|
| 148 |
</button>
|
|
@@ -158,43 +158,43 @@ export function ScopeRealismWidget({ state, openModal }) {
|
|
| 158 |
const REVIEW_TEMPLATES = [
|
| 159 |
{
|
| 160 |
severity: 8,
|
| 161 |
-
major: 'The
|
| 162 |
minor: [
|
| 163 |
-
'
|
| 164 |
-
'
|
| 165 |
-
'
|
| 166 |
-
'
|
| 167 |
],
|
| 168 |
suggestions: [
|
| 169 |
-
'Add
|
| 170 |
-
'
|
| 171 |
-
'
|
| 172 |
],
|
| 173 |
},
|
| 174 |
{
|
| 175 |
severity: 7,
|
| 176 |
-
major: 'You claim
|
| 177 |
minor: [
|
| 178 |
-
'
|
| 179 |
-
'No statement of what would change
|
| 180 |
-
'
|
| 181 |
],
|
| 182 |
suggestions: [
|
| 183 |
-
'Replace "
|
| 184 |
-
'List 1
|
| 185 |
],
|
| 186 |
},
|
| 187 |
{
|
| 188 |
severity: 9,
|
| 189 |
-
major: 'This reads like
|
| 190 |
minor: [
|
| 191 |
-
'
|
| 192 |
-
'"
|
| 193 |
-
'No mention of
|
| 194 |
],
|
| 195 |
suggestions: [
|
| 196 |
-
'Lead sentence: "
|
| 197 |
-
'Cut "
|
| 198 |
],
|
| 199 |
},
|
| 200 |
];
|
|
@@ -313,10 +313,10 @@ export function ReviewerModal({ data, onClose }) {
|
|
| 313 |
}
|
| 314 |
|
| 315 |
const HARDER_COUNTERS = [
|
| 316 |
-
{ lbl: 'Reverse causation', text: 'You assume
|
| 317 |
-
{ lbl: '
|
| 318 |
-
{ lbl: 'Wrong
|
| 319 |
-
{ lbl: '
|
| 320 |
];
|
| 321 |
|
| 322 |
export function DevilsModal({ data, onClose }) {
|
|
@@ -333,7 +333,7 @@ export function DevilsModal({ data, onClose }) {
|
|
| 333 |
data.onUpdate({ counters: nc });
|
| 334 |
fireToast('Stronger counter added: "' + next.lbl + '"', 'critic');
|
| 335 |
} else {
|
| 336 |
-
fireToast('No more counters — your
|
| 337 |
}
|
| 338 |
setPushing(false);
|
| 339 |
}, 800);
|
|
@@ -345,7 +345,7 @@ export function DevilsModal({ data, onClose }) {
|
|
| 345 |
<div className="modal-icon critic"><Icon name="scale" size={18}/></div>
|
| 346 |
<div style={{ flex: 1 }}>
|
| 347 |
<div className="modal-title">Devil's Advocate</div>
|
| 348 |
-
<div className="modal-sub">The strongest counter-arguments to your
|
| 349 |
</div>
|
| 350 |
<button className="icon-btn" onClick={onClose}><Icon name="x" size={16}/></button>
|
| 351 |
</div>
|
|
@@ -414,10 +414,10 @@ export function ScopeModal({ data, onClose }) {
|
|
| 414 |
<div style={{ marginTop: 16 }}>
|
| 415 |
<div className="label" style={{ marginBottom: 8 }}>Recommended actions</div>
|
| 416 |
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 13, color: 'var(--canvas-text-2)', lineHeight: 1.6 }}>
|
| 417 |
-
<li><strong style={{ color: 'var(--canvas-text)' }}>Commit to
|
| 418 |
-
<li>Build a
|
| 419 |
-
<li>Cut a
|
| 420 |
-
<li>Calibrate against
|
| 421 |
</ul>
|
| 422 |
</div>
|
| 423 |
</div>
|
|
|
|
| 97 |
className="btn"
|
| 98 |
title="Open Devil's Advocate in the main chat (history lives there)"
|
| 99 |
onClick={() => handoffToChat("Devil's Advocate",
|
| 100 |
+
`Take the position of devil's advocate on my claim: "${state.claim || 'my current training plan'}". Be ruthless.`)}
|
| 101 |
>
|
| 102 |
<Icon name="message" size={13}/>Open in chat
|
| 103 |
</button>
|
|
|
|
| 142 |
className="btn"
|
| 143 |
title="Open Scope Realism in the main chat (history lives there)"
|
| 144 |
onClick={() => handoffToChat('Scope Realism',
|
| 145 |
+
`Run a brutal feasibility check on my goal: "${state.target || 'my current training goal'}". Be specific about what's at risk.`)}
|
| 146 |
>
|
| 147 |
<Icon name="message" size={13}/>Open in chat
|
| 148 |
</button>
|
|
|
|
| 158 |
const REVIEW_TEMPLATES = [
|
| 159 |
{
|
| 160 |
severity: 8,
|
| 161 |
+
major: 'The program states a goal ("get bigger and stronger") before defining what success looks like. You never specify the measurable target — which lifts, how much, by when — so there is no way to tell if the plan is working or when to change it.',
|
| 162 |
minor: [
|
| 163 |
+
'Weekly volume is described as "high" without a set count per muscle group.',
|
| 164 |
+
'Progression is listed as "add weight when you can" with no concrete rule (reps in reserve? double progression?).',
|
| 165 |
+
'Legs get one day while chest gets two, with no rationale for the imbalance.',
|
| 166 |
+
'No deload is scheduled across a 12-week block — fatigue will accumulate unmanaged.',
|
| 167 |
],
|
| 168 |
suggestions: [
|
| 169 |
+
'Add one sentence naming the target: e.g. "bench 100kg for 5, add 5cm on arms in 12 weeks."',
|
| 170 |
+
'Pick an explicit progression rule and write it down.',
|
| 171 |
+
'Schedule a deload every 5–6 weeks and rebalance leg volume.',
|
| 172 |
],
|
| 173 |
},
|
| 174 |
{
|
| 175 |
severity: 7,
|
| 176 |
+
major: 'You claim this split is "optimal for hypertrophy." That word is doing too much work. The layout is also compatible with several worse outcomes (junk volume, under-recovery, skipped legs). Without a way to check whether volume actually drives growth for you, "optimal" is untestable.',
|
| 177 |
minor: [
|
| 178 |
+
'The split is justified by "that\'s what most lifters do" rather than by your schedule and recovery.',
|
| 179 |
+
'No statement of what would make you change the plan.',
|
| 180 |
+
'A program with no logged numbers is a red flag — you can\'t audit it later.',
|
| 181 |
],
|
| 182 |
suggestions: [
|
| 183 |
+
'Replace "optimal" with a specific target the plan either hits or misses.',
|
| 184 |
+
'List 1–2 signals (stalled lifts, poor sleep) that would trigger a change.',
|
| 185 |
],
|
| 186 |
},
|
| 187 |
{
|
| 188 |
severity: 9,
|
| 189 |
+
major: 'This reads like a wish list, not a plan. There is no number. There is no timeframe. The strongest claim is that you want to "tone up and feel better" — which is the lowest possible bar. If you actually care about the result, lead with the measurable goal, not the vibe.',
|
| 190 |
minor: [
|
| 191 |
+
'The word "consistent" appears three times in three sentences. Show it with a logged history instead.',
|
| 192 |
+
'"Functional training" is jargon-without-definition; say what you\'ll actually do.',
|
| 193 |
+
'No mention of nutrition, despite it driving most of the outcome you want.',
|
| 194 |
],
|
| 195 |
suggestions: [
|
| 196 |
+
'Lead sentence: "In 12 weeks I will <specific, measurable result>."',
|
| 197 |
+
'Cut "I want to get in shape" entirely. Replace it with a lift, a weight, and a date.',
|
| 198 |
],
|
| 199 |
},
|
| 200 |
];
|
|
|
|
| 313 |
}
|
| 314 |
|
| 315 |
const HARDER_COUNTERS = [
|
| 316 |
+
{ lbl: 'Reverse causation', text: 'You assume the program drove your gains. The opposite is just as likely early on — newbie gains and better sleep would have driven progress on almost any plan. You may be crediting the split for something else.' },
|
| 317 |
+
{ lbl: 'Goalpost shift', text: 'When the scale doesn\'t move you\'ll be tempted to redefine "progress" until it does (now it\'s "recomp," now it\'s "strength"). Pick your primary metric up front or you can\'t honestly say the plan worked.' },
|
| 318 |
+
{ lbl: 'Wrong lever', text: 'You\'re tweaking exercise selection when the real bottleneck is total volume and calories. You\'re optimizing the variable that matters least.' },
|
| 319 |
+
{ lbl: 'Copied context', text: 'This program was built for a lifter training 5 days a week with years of experience. On your schedule and recovery it may be testing a plan that doesn\'t apply to you.' },
|
| 320 |
];
|
| 321 |
|
| 322 |
export function DevilsModal({ data, onClose }) {
|
|
|
|
| 333 |
data.onUpdate({ counters: nc });
|
| 334 |
fireToast('Stronger counter added: "' + next.lbl + '"', 'critic');
|
| 335 |
} else {
|
| 336 |
+
fireToast('No more counters — your plan is more robust than I thought.');
|
| 337 |
}
|
| 338 |
setPushing(false);
|
| 339 |
}, 800);
|
|
|
|
| 345 |
<div className="modal-icon critic"><Icon name="scale" size={18}/></div>
|
| 346 |
<div style={{ flex: 1 }}>
|
| 347 |
<div className="modal-title">Devil's Advocate</div>
|
| 348 |
+
<div className="modal-sub">The strongest counter-arguments to your plan, ranked by how much they should worry you.</div>
|
| 349 |
</div>
|
| 350 |
<button className="icon-btn" onClick={onClose}><Icon name="x" size={16}/></button>
|
| 351 |
</div>
|
|
|
|
| 414 |
<div style={{ marginTop: 16 }}>
|
| 415 |
<div className="label" style={{ marginBottom: 8 }}>Recommended actions</div>
|
| 416 |
<ul style={{ margin: 0, paddingLeft: 18, fontSize: 13, color: 'var(--canvas-text-2)', lineHeight: 1.6 }}>
|
| 417 |
+
<li><strong style={{ color: 'var(--canvas-text)' }}>Commit to one primary goal for this block.</strong> Trying to bulk and cut at once is your bottleneck, not effort.</li>
|
| 418 |
+
<li>Build a consistency buffer. Your 9-day streak is great; 90 days is the minimum to absorb the inevitable work/family/illness setbacks.</li>
|
| 419 |
+
<li>Cut a training day. A 3-day plan you actually run beats a 5-day one you skip.</li>
|
| 420 |
+
<li>Calibrate against reality: a natural lifter gains ~0.25–0.5% bodyweight per week when bulking. Your projected rate is faster than that.</li>
|
| 421 |
</ul>
|
| 422 |
</div>
|
| 423 |
</div>
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
// Deliverables view — multi-project
|
| 2 |
-
// Each "project" is a draft of a template (
|
| 3 |
// You can keep many projects in flight, switch between them, version-rollback,
|
| 4 |
-
// drag in
|
| 5 |
// and export to Markdown / LaTeX / HTML / Print.
|
| 6 |
// AI passes are stubbed (need LLM endpoint) but every static signal works today.
|
| 7 |
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
@@ -32,193 +32,193 @@ const newId = (p) => p + Math.random().toString(36).slice(2, 8);
|
|
| 32 |
export const TEMPLATES = [
|
| 33 |
{
|
| 34 |
id: 'research-paper',
|
| 35 |
-
name: '
|
| 36 |
-
desc: '
|
| 37 |
icon: 'book',
|
| 38 |
mode: 'paper',
|
| 39 |
sections: [
|
| 40 |
-
{ id: 'abstract', name: '
|
| 41 |
-
{ id: 'intro', name: '
|
| 42 |
-
{ id: 'methods', name: '
|
| 43 |
-
{ id: 'results', name: '
|
| 44 |
-
{ id: 'discussion', name: '
|
| 45 |
-
{ id: 'refs', name: '
|
| 46 |
],
|
| 47 |
},
|
| 48 |
{
|
| 49 |
id: 'thesis-chapter',
|
| 50 |
-
name: '
|
| 51 |
-
desc: '
|
| 52 |
icon: 'book',
|
| 53 |
mode: 'paper',
|
| 54 |
sections: [
|
| 55 |
-
{ id: 'overview', name: 'Overview', target: 200, hint: 'What this
|
| 56 |
-
{ id: 'background', name: '
|
| 57 |
-
{ id: 'methods', name: '
|
| 58 |
-
{ id: 'results', name: 'Results', target: 2000, hint: '
|
| 59 |
-
{ id: 'discussion', name: '
|
| 60 |
],
|
| 61 |
},
|
| 62 |
{
|
| 63 |
id: 'nsf-grfp',
|
| 64 |
-
name: '
|
| 65 |
-
desc: '
|
| 66 |
icon: 'award',
|
| 67 |
mode: 'document',
|
| 68 |
sections: [
|
| 69 |
-
{ id: 'personal', name: '
|
| 70 |
-
{ id: 'research', name: '
|
| 71 |
],
|
| 72 |
},
|
| 73 |
{
|
| 74 |
id: 'conference-abstract',
|
| 75 |
-
name: '
|
| 76 |
-
desc: 'Single section, 250 words. Lead with the
|
| 77 |
icon: 'send',
|
| 78 |
mode: 'document',
|
| 79 |
sections: [
|
| 80 |
-
{ id: 'abs', name: '
|
| 81 |
],
|
| 82 |
},
|
| 83 |
{
|
| 84 |
id: 'defense-slides',
|
| 85 |
-
name: '
|
| 86 |
-
desc: 'Title → Outline → Background →
|
| 87 |
icon: 'kanban',
|
| 88 |
mode: 'slides',
|
| 89 |
sections: [
|
| 90 |
-
{ id: 'title', name: 'Title slide', target: 30, hint: '
|
| 91 |
-
{ id: 'outline', name: 'Outline', target: 60, hint: '5–7 bullet points covering the
|
| 92 |
-
{ id: 'background', name: 'Background', target: 200, hint: '
|
| 93 |
-
{ id: 'question', name: '
|
| 94 |
-
{ id: 'methods', name: '
|
| 95 |
-
{ id: 'results', name: '
|
| 96 |
-
{ id: 'discussion', name: 'Discussion', target: 200, hint: '
|
| 97 |
{ id: 'qa', name: 'Anticipated Q&A', target: 300, hint: 'Hardest 5 questions and your answers.', checks: [] },
|
| 98 |
],
|
| 99 |
},
|
| 100 |
{
|
| 101 |
id: 'poster',
|
| 102 |
-
name: '
|
| 103 |
-
desc: '4-quadrant
|
| 104 |
icon: 'layout',
|
| 105 |
mode: 'poster',
|
| 106 |
sections: [
|
| 107 |
-
{ id: 'title', name: 'Title &
|
| 108 |
-
{ id: 'intro', name: '
|
| 109 |
-
{ id: 'methods', name: '
|
| 110 |
-
{ id: 'results', name: 'Results', target: 250, hint: 'Headline
|
| 111 |
-
{ id: 'discussion', name: '
|
| 112 |
-
{ id: 'refs', name: '
|
| 113 |
],
|
| 114 |
},
|
| 115 |
{
|
| 116 |
id: 'cv',
|
| 117 |
-
name: '
|
| 118 |
-
desc: 'Standard sections:
|
| 119 |
icon: 'user',
|
| 120 |
mode: 'document',
|
| 121 |
sections: [
|
| 122 |
-
{ id: 'header', name: 'Header', target: 40, hint: 'Name,
|
| 123 |
-
{ id: 'education', name: '
|
| 124 |
-
{ id: 'publications', name: '
|
| 125 |
-
{ id: 'talks', name: '
|
| 126 |
-
{ id: 'awards', name: '
|
| 127 |
-
{ id: 'service', name: '
|
| 128 |
-
{ id: 'skills', name: 'Skills', target: 60, hint: '
|
| 129 |
],
|
| 130 |
},
|
| 131 |
{
|
| 132 |
id: 'cover-letter',
|
| 133 |
-
name: '
|
| 134 |
-
desc: 'For
|
| 135 |
icon: 'send',
|
| 136 |
mode: 'document',
|
| 137 |
sections: [
|
| 138 |
-
{ id: 'header', name: 'Header', target: 40, hint: 'Date, recipient,
|
| 139 |
-
{ id: 'opener', name: 'Opening paragraph', target: 100, hint: 'Why you\'re
|
| 140 |
-
{ id: 'body', name: '
|
| 141 |
-
{ id: 'fit', name: 'Why this
|
| 142 |
{ id: 'close', name: 'Close', target: 60, hint: 'Thanks + next step + signature.', checks: [] },
|
| 143 |
],
|
| 144 |
},
|
| 145 |
{
|
| 146 |
id: 'irb-protocol',
|
| 147 |
-
name: '
|
| 148 |
-
desc: '
|
| 149 |
icon: 'shield',
|
| 150 |
mode: 'document',
|
| 151 |
sections: [
|
| 152 |
-
{ id: 'overview', name: '
|
| 153 |
-
{ id: 'background', name: '
|
| 154 |
-
{ id: 'aims', name: '
|
| 155 |
-
{ id: 'participants', name: '
|
| 156 |
-
{ id: 'procedures', name: '
|
| 157 |
-
{ id: 'risks', name: 'Risks &
|
| 158 |
-
{ id: 'benefits', name: '
|
| 159 |
-
{ id: 'consent', name: '
|
| 160 |
-
{ id: 'data', name: '
|
| 161 |
],
|
| 162 |
},
|
| 163 |
{
|
| 164 |
id: 'meeting-prep',
|
| 165 |
-
name: '
|
| 166 |
-
desc: 'Bring this to your
|
| 167 |
icon: 'message',
|
| 168 |
mode: 'document',
|
| 169 |
sections: [
|
| 170 |
{ id: 'agenda', name: 'Agenda', target: 80, hint: '3–5 bullets ranked by priority.', checks: [] },
|
| 171 |
-
{ id: 'progress', name: 'Progress since last
|
| 172 |
-
{ id: 'blockers', name: 'Blockers', target: 150, hint: 'What
|
| 173 |
-
{ id: 'decisions', name: 'Decisions needed', target: 200, hint: 'Frame as A/B options with your
|
| 174 |
-
{ id: 'questions', name: 'Questions', target: 150, hint: 'Open questions
|
| 175 |
-
{ id: 'followup', name: 'Action items (post-
|
| 176 |
],
|
| 177 |
},
|
| 178 |
{
|
| 179 |
id: 'dissertation-formatting',
|
| 180 |
-
name: '
|
| 181 |
-
desc: 'Catch-everything pass before
|
| 182 |
icon: 'shield',
|
| 183 |
mode: 'document',
|
| 184 |
sections: [
|
| 185 |
-
{ id: 'frontmatter', name: '
|
| 186 |
-
{ id: 'margins', name: '
|
| 187 |
-
{ id: 'fonts', name: '
|
| 188 |
-
{ id: 'pagenumbers', name: '
|
| 189 |
-
{ id: 'figures', name: '
|
| 190 |
-
{ id: 'citations', name: '
|
| 191 |
-
{ id: 'appendices', name: '
|
| 192 |
-
{ id: 'proquest', name: '
|
| 193 |
],
|
| 194 |
},
|
| 195 |
{
|
| 196 |
id: 'faculty-hunt',
|
| 197 |
-
name: '
|
| 198 |
-
desc: 'For
|
| 199 |
icon: 'user',
|
| 200 |
mode: 'document',
|
| 201 |
sections: [
|
| 202 |
-
{ id: 'criteria', name: 'What you\'re looking for', target: 150, hint: '
|
| 203 |
-
{ id: 'shortlist', name: 'Shortlist (5–10
|
| 204 |
-
{ id: 'pubs', name: '
|
| 205 |
-
{ id: 'students', name: 'Current/recent
|
| 206 |
-
{ id: 'reachout', name: 'Outreach plan', target: 200, hint: 'When to
|
| 207 |
-
{ id: 'notes', name: 'Conversation notes', target: 0, hint: 'After
|
| 208 |
],
|
| 209 |
},
|
| 210 |
{
|
| 211 |
id: 'research-statement',
|
| 212 |
-
name: '
|
| 213 |
-
desc: '
|
| 214 |
icon: 'sparkles',
|
| 215 |
mode: 'document',
|
| 216 |
sections: [
|
| 217 |
-
{ id: 'overview', name: 'Overview', target: 200, hint: 'One paragraph: your
|
| 218 |
-
{ id: 'past', name: '
|
| 219 |
-
{ id: 'current', name: '
|
| 220 |
-
{ id: 'future', name: '
|
| 221 |
-
{ id: 'broader', name: '
|
| 222 |
],
|
| 223 |
},
|
| 224 |
];
|
|
@@ -466,7 +466,7 @@ const DeliverablesView = ({ allStates }) => {
|
|
| 466 |
<div>
|
| 467 |
<h1 className="page-title">Documents</h1>
|
| 468 |
<div className="page-sub">
|
| 469 |
-
|
| 470 |
{projectList.length > 0 && ` · ${projectList.length} draft${projectList.length === 1 ? '' : 's'} in flight.`}
|
| 471 |
</div>
|
| 472 |
</div>
|
|
@@ -504,7 +504,7 @@ const DeliverablesView = ({ allStates }) => {
|
|
| 504 |
<div className="canvas-presets">
|
| 505 |
<div className="canvas-presets-head">
|
| 506 |
<div className="canvas-presets-title">{projectList.length > 0 ? 'Or start a new draft' : 'Pick a template'}</div>
|
| 507 |
-
<div className="canvas-presets-sub">
|
| 508 |
</div>
|
| 509 |
<div className="canvas-presets-grid">
|
| 510 |
{TEMPLATES.map(t => (
|
|
@@ -877,7 +877,7 @@ const DeliverablesView = ({ allStates }) => {
|
|
| 877 |
<div className={`notion-page ${paperLike ? 'serif' : ''}`}>
|
| 878 |
<h1 className="notion-page-title">{project.name}</h1>
|
| 879 |
<div className="notion-page-meta">
|
| 880 |
-
{totalWords} words · ~{readingMinutes(totalWords)} min read · {template.sections.length} sections{paperLike ? ' ·
|
| 881 |
</div>
|
| 882 |
{template.sections.map(s => {
|
| 883 |
const text = sections[s.id] || '';
|
|
|
|
| 1 |
+
// Deliverables view — multi-project document center.
|
| 2 |
+
// Each "project" is a draft of a template (program plan, check-in, poster, résumé, etc.).
|
| 3 |
// You can keep many projects in flight, switch between them, version-rollback,
|
| 4 |
+
// drag in references from your Bibliography, embed images, render math,
|
| 5 |
// and export to Markdown / LaTeX / HTML / Print.
|
| 6 |
// AI passes are stubbed (need LLM endpoint) but every static signal works today.
|
| 7 |
import React, { useState, useMemo, useEffect, useRef } from 'react';
|
|
|
|
| 32 |
export const TEMPLATES = [
|
| 33 |
{
|
| 34 |
id: 'research-paper',
|
| 35 |
+
name: 'Training Program Plan',
|
| 36 |
+
desc: 'Overview → Goals & assessment → Weekly split → Progression → Nutrition → Resources',
|
| 37 |
icon: 'book',
|
| 38 |
mode: 'paper',
|
| 39 |
sections: [
|
| 40 |
+
{ id: 'abstract', name: 'Overview', target: 250, hint: 'One paragraph: goal, timeframe, split, and expected outcome.', checks: ['hasNumber', 'hasFinding'] },
|
| 41 |
+
{ id: 'intro', name: 'Goals & assessment', target: 1000, hint: 'Current lifts and measurements, target, and any limitations.', checks: ['hasNumber'] },
|
| 42 |
+
{ id: 'methods', name: 'Weekly split', target: 800, hint: 'Day-by-day: exercises, sets, reps, and rest.', checks: ['hasNumber'] },
|
| 43 |
+
{ id: 'results', name: 'Progression scheme', target: 800, hint: 'How you add load or volume week to week. Deload timing.', checks: ['hasNumber'] },
|
| 44 |
+
{ id: 'discussion', name: 'Nutrition plan', target: 1000, hint: 'Calorie and protein targets, meal timing, supplements.', checks: ['hasNumber'] },
|
| 45 |
+
{ id: 'refs', name: 'Resources', target: 0, hint: 'Links to programs, exercise demos, and references.', checks: [] },
|
| 46 |
],
|
| 47 |
},
|
| 48 |
{
|
| 49 |
id: 'thesis-chapter',
|
| 50 |
+
name: 'Training Block Review',
|
| 51 |
+
desc: 'Overview → Starting point → What you did → Results → What to change',
|
| 52 |
icon: 'book',
|
| 53 |
mode: 'paper',
|
| 54 |
sections: [
|
| 55 |
+
{ id: 'overview', name: 'Overview', target: 200, hint: 'What block this covers and its goal.', checks: [] },
|
| 56 |
+
{ id: 'background', name: 'Starting point', target: 1500, hint: 'Lifts, bodyweight, and condition at the start of the block.', checks: ['hasNumber'] },
|
| 57 |
+
{ id: 'methods', name: 'What you did', target: 1500, hint: 'Training approach, volume, and adherence.', checks: ['hasNumber'] },
|
| 58 |
+
{ id: 'results', name: 'Results', target: 2000, hint: 'Strength, size, and body-composition changes with numbers.', checks: ['hasNumber'] },
|
| 59 |
+
{ id: 'discussion', name: 'What to change', target: 1500, hint: 'What worked, what didn\'t, and adjustments for next block.', checks: ['hasLimit'] },
|
| 60 |
],
|
| 61 |
},
|
| 62 |
{
|
| 63 |
id: 'nsf-grfp',
|
| 64 |
+
name: 'Coaching Intake',
|
| 65 |
+
desc: 'Athlete background + goal plan for a new coaching relationship.',
|
| 66 |
icon: 'award',
|
| 67 |
mode: 'document',
|
| 68 |
sections: [
|
| 69 |
+
{ id: 'personal', name: 'Athlete background', target: 1500, hint: 'Training history, injuries, lifestyle, and motivation. Write as a story.', checks: [] },
|
| 70 |
+
{ id: 'research', name: 'Goal plan', target: 1000, hint: 'Specific goal, timeframe, and how you plan to get there.', checks: ['hasNumber'] },
|
| 71 |
],
|
| 72 |
},
|
| 73 |
{
|
| 74 |
id: 'conference-abstract',
|
| 75 |
+
name: 'Weekly Check-in',
|
| 76 |
+
desc: 'Single section, ~250 words. Lead with the wins and the numbers.',
|
| 77 |
icon: 'send',
|
| 78 |
mode: 'document',
|
| 79 |
sections: [
|
| 80 |
+
{ id: 'abs', name: 'Check-in', target: 250, hint: 'One paragraph: weight, training, nutrition, and how you feel.', checks: ['hasFinding', 'hasNumber'] },
|
| 81 |
],
|
| 82 |
},
|
| 83 |
{
|
| 84 |
id: 'defense-slides',
|
| 85 |
+
name: 'Program Presentation',
|
| 86 |
+
desc: 'Title → Outline → Background → Goal → Approach → Results → Discussion → Q&A',
|
| 87 |
icon: 'kanban',
|
| 88 |
mode: 'slides',
|
| 89 |
sections: [
|
| 90 |
+
{ id: 'title', name: 'Title slide', target: 30, hint: 'Program name, your name, date.', checks: [] },
|
| 91 |
+
{ id: 'outline', name: 'Outline', target: 60, hint: '5–7 bullet points covering the plan.', checks: [] },
|
| 92 |
+
{ id: 'background', name: 'Background', target: 200, hint: 'Starting point and why this program.', checks: [] },
|
| 93 |
+
{ id: 'question', name: 'Goal', target: 80, hint: 'Single, measurable goal.', checks: [] },
|
| 94 |
+
{ id: 'methods', name: 'Approach', target: 200, hint: 'High-level: split, volume, progression.', checks: ['hasNumber'] },
|
| 95 |
+
{ id: 'results', name: 'Expected results', target: 300, hint: 'Realistic targets per milestone.', checks: ['hasNumber'] },
|
| 96 |
+
{ id: 'discussion', name: 'Discussion', target: 200, hint: 'Risks, recovery, and next steps.', checks: ['hasLimit'] },
|
| 97 |
{ id: 'qa', name: 'Anticipated Q&A', target: 300, hint: 'Hardest 5 questions and your answers.', checks: [] },
|
| 98 |
],
|
| 99 |
},
|
| 100 |
{
|
| 101 |
id: 'poster',
|
| 102 |
+
name: 'Progress Poster',
|
| 103 |
+
desc: '4-quadrant progress board: Goal · Approach · Results · Next.',
|
| 104 |
icon: 'layout',
|
| 105 |
mode: 'poster',
|
| 106 |
sections: [
|
| 107 |
+
{ id: 'title', name: 'Title & Athlete', target: 30, hint: 'Program name, your name, phase.', checks: [] },
|
| 108 |
+
{ id: 'intro', name: 'Goal', target: 200, hint: 'Where you started and where you\'re headed.', checks: [] },
|
| 109 |
+
{ id: 'methods', name: 'Approach', target: 200, hint: 'High-level: split, volume, nutrition.', checks: ['hasNumber'] },
|
| 110 |
+
{ id: 'results', name: 'Results', target: 250, hint: 'Headline numbers + before/after.', checks: ['hasNumber'] },
|
| 111 |
+
{ id: 'discussion', name: 'Next steps', target: 200, hint: 'What it means + what\'s next.', checks: ['hasLimit'] },
|
| 112 |
+
{ id: 'refs', name: 'Notes / Credits', target: 80, hint: 'Program source, coach, contact.', checks: [] },
|
| 113 |
],
|
| 114 |
},
|
| 115 |
{
|
| 116 |
id: 'cv',
|
| 117 |
+
name: 'Lifting Résumé',
|
| 118 |
+
desc: 'Standard sections: Stats · PRs · Competitions · Certifications · Skills.',
|
| 119 |
icon: 'user',
|
| 120 |
mode: 'document',
|
| 121 |
sections: [
|
| 122 |
+
{ id: 'header', name: 'Header', target: 40, hint: 'Name, bodyweight, training age, contact.', checks: [] },
|
| 123 |
+
{ id: 'education', name: 'Training history', target: 100, hint: 'Most recent first. Program · Duration · Focus.', checks: [] },
|
| 124 |
+
{ id: 'publications', name: 'Personal records', target: 300, hint: 'Big lifts and key numbers. Group by movement.', checks: ['hasNumber'] },
|
| 125 |
+
{ id: 'talks', name: 'Competitions & events', target: 150, hint: 'Meet · Placing · Year.', checks: [] },
|
| 126 |
+
{ id: 'awards', name: 'Achievements', target: 100, hint: 'Most recent first. Milestone + date.', checks: [] },
|
| 127 |
+
{ id: 'service', name: 'Coaching & mentorship', target: 100, hint: 'Anyone you\'ve trained or helped.', checks: [] },
|
| 128 |
+
{ id: 'skills', name: 'Skills', target: 60, hint: 'Lifts, sports, mobility, and specialties.', checks: [] },
|
| 129 |
],
|
| 130 |
},
|
| 131 |
{
|
| 132 |
id: 'cover-letter',
|
| 133 |
+
name: 'Coach Outreach Letter',
|
| 134 |
+
desc: 'For reaching out to a coach, gym, or training partner.',
|
| 135 |
icon: 'send',
|
| 136 |
mode: 'document',
|
| 137 |
sections: [
|
| 138 |
+
{ id: 'header', name: 'Header', target: 40, hint: 'Date, recipient, greeting.', checks: [] },
|
| 139 |
+
{ id: 'opener', name: 'Opening paragraph', target: 100, hint: 'Why you\'re reaching out + your goal.', checks: [] },
|
| 140 |
+
{ id: 'body', name: 'Where you\'re at', target: 250, hint: 'Current stats and training background. Numbers > adjectives.', checks: ['hasNumber'] },
|
| 141 |
+
{ id: 'fit', name: 'Why this coach', target: 150, hint: 'What about them makes it the right fit.', checks: [] },
|
| 142 |
{ id: 'close', name: 'Close', target: 60, hint: 'Thanks + next step + signature.', checks: [] },
|
| 143 |
],
|
| 144 |
},
|
| 145 |
{
|
| 146 |
id: 'irb-protocol',
|
| 147 |
+
name: 'Nutrition Protocol',
|
| 148 |
+
desc: 'A structured plan for a diet phase — targets, meals, and tracking.',
|
| 149 |
icon: 'shield',
|
| 150 |
mode: 'document',
|
| 151 |
sections: [
|
| 152 |
+
{ id: 'overview', name: 'Diet overview', target: 200, hint: 'One-paragraph summary of the phase (bulk, cut, maintain).', checks: [] },
|
| 153 |
+
{ id: 'background', name: 'Goal & rationale', target: 400, hint: 'Why this phase? What are you optimizing for?', checks: [] },
|
| 154 |
+
{ id: 'aims', name: 'Targets', target: 250, hint: 'Calories, protein, carbs, fats, and rate of change.', checks: ['hasNumber'] },
|
| 155 |
+
{ id: 'participants', name: 'Your stats', target: 300, hint: 'Bodyweight, measurements, and maintenance estimate.', checks: ['hasNumber'] },
|
| 156 |
+
{ id: 'procedures', name: 'Meal plan', target: 500, hint: 'Day structure, meal timing, and go-to foods.', checks: ['hasNumber'] },
|
| 157 |
+
{ id: 'risks', name: 'Risks & adjustments', target: 200, hint: 'Hunger, energy, plateaus — and how you\'ll adjust.', checks: ['hasLimit'] },
|
| 158 |
+
{ id: 'benefits', name: 'Expected outcome', target: 100, hint: 'Realistic result over the phase.', checks: [] },
|
| 159 |
+
{ id: 'consent', name: 'Adherence plan', target: 200, hint: 'How you\'ll stay consistent: prep, flexibility, refeeds.', checks: [] },
|
| 160 |
+
{ id: 'data', name: 'Tracking', target: 200, hint: 'What you log, how often, and how you review it.', checks: [] },
|
| 161 |
],
|
| 162 |
},
|
| 163 |
{
|
| 164 |
id: 'meeting-prep',
|
| 165 |
+
name: 'Coach Check-in Prep',
|
| 166 |
+
desc: 'Bring this to your check-in — agenda, updates, decisions needed, follow-ups.',
|
| 167 |
icon: 'message',
|
| 168 |
mode: 'document',
|
| 169 |
sections: [
|
| 170 |
{ id: 'agenda', name: 'Agenda', target: 80, hint: '3–5 bullets ranked by priority.', checks: [] },
|
| 171 |
+
{ id: 'progress', name: 'Progress since last check-in', target: 200, hint: 'What you actually did, with numbers when possible.', checks: ['hasNumber'] },
|
| 172 |
+
{ id: 'blockers', name: 'Blockers', target: 150, hint: 'What\'s getting in the way of progress.', checks: ['hasLimit'] },
|
| 173 |
+
{ id: 'decisions', name: 'Decisions needed', target: 200, hint: 'Frame as A/B options with your preference.', checks: [] },
|
| 174 |
+
{ id: 'questions', name: 'Questions', target: 150, hint: 'Open questions for your coach.', checks: [] },
|
| 175 |
+
{ id: 'followup', name: 'Action items (post check-in)', target: 100, hint: 'Fill in during/after. What to do + by when.', checks: [] },
|
| 176 |
],
|
| 177 |
},
|
| 178 |
{
|
| 179 |
id: 'dissertation-formatting',
|
| 180 |
+
name: 'Meet Prep Checklist',
|
| 181 |
+
desc: 'Catch-everything pass in the weeks before a competition or photoshoot.',
|
| 182 |
icon: 'shield',
|
| 183 |
mode: 'document',
|
| 184 |
sections: [
|
| 185 |
+
{ id: 'frontmatter', name: 'Logistics', target: 0, hint: 'Registration, weigh-in time, venue, travel, and gear list.', checks: [] },
|
| 186 |
+
{ id: 'margins', name: 'Weight & conditioning', target: 0, hint: 'Target weight class and how you\'ll get there safely.', checks: ['hasNumber'] },
|
| 187 |
+
{ id: 'fonts', name: 'Peak week plan', target: 0, hint: 'Training taper, carbs, sodium, and water strategy.', checks: ['hasNumber'] },
|
| 188 |
+
{ id: 'pagenumbers', name: 'Attempt / posing plan', target: 0, hint: 'Opener/second/third attempts, or posing routine.', checks: [] },
|
| 189 |
+
{ id: 'figures', name: 'Gear & equipment', target: 0, hint: 'Singlet/belt/sleeves/shoes or posing suit and tan.', checks: [] },
|
| 190 |
+
{ id: 'citations', name: 'Nutrition & timing', target: 0, hint: 'Pre-event meals and intra-event fueling.', checks: [] },
|
| 191 |
+
{ id: 'appendices', name: 'Recovery & warm-up', target: 0, hint: 'Warm-up sequence and between-attempt recovery.', checks: [] },
|
| 192 |
+
{ id: 'proquest', name: 'Day-of checklist', target: 0, hint: 'Final pass: bag packed, alarms set, food prepped.', checks: [] },
|
| 193 |
],
|
| 194 |
},
|
| 195 |
{
|
| 196 |
id: 'faculty-hunt',
|
| 197 |
+
name: 'Coach / Gym Hunt',
|
| 198 |
+
desc: 'For finding a coach or gym — research the options before committing.',
|
| 199 |
icon: 'user',
|
| 200 |
mode: 'document',
|
| 201 |
sections: [
|
| 202 |
+
{ id: 'criteria', name: 'What you\'re looking for', target: 150, hint: 'Specialty, coaching style, location, and budget.', checks: [] },
|
| 203 |
+
{ id: 'shortlist', name: 'Shortlist (5–10 options)', target: 400, hint: 'For each: name, location, focus, why they fit.', checks: [] },
|
| 204 |
+
{ id: 'pubs', name: 'Reputation & results', target: 300, hint: 'Reviews, client transformations, credentials.', checks: [] },
|
| 205 |
+
{ id: 'students', name: 'Current/recent clients', target: 200, hint: 'Who they train and the results they get.', checks: ['hasNumber'] },
|
| 206 |
+
{ id: 'reachout', name: 'Outreach plan', target: 200, hint: 'When to contact, what to ask, trial options.', checks: [] },
|
| 207 |
+
{ id: 'notes', name: 'Conversation notes', target: 0, hint: 'After calls/visits — vibes, fit signals, red flags.', checks: [] },
|
| 208 |
],
|
| 209 |
},
|
| 210 |
{
|
| 211 |
id: 'research-statement',
|
| 212 |
+
name: 'Training Philosophy',
|
| 213 |
+
desc: 'Your approach to training: where you\'ve been, where you are, where you\'re going.',
|
| 214 |
icon: 'sparkles',
|
| 215 |
mode: 'document',
|
| 216 |
sections: [
|
| 217 |
+
{ id: 'overview', name: 'Overview', target: 200, hint: 'One paragraph: your training identity in 2–3 sentences.', checks: [] },
|
| 218 |
+
{ id: 'past', name: 'Where you\'ve been', target: 600, hint: 'Your training history. Lead with the results you got.', checks: ['hasFinding'] },
|
| 219 |
+
{ id: 'current', name: 'Where you are now', target: 400, hint: 'Current focus and why it matters to you.', checks: [] },
|
| 220 |
+
{ id: 'future', name: 'Where you\'re going', target: 600, hint: '1–3 year arc with concrete milestones.', checks: ['hasNumber'] },
|
| 221 |
+
{ id: 'broader', name: 'Beyond the gym', target: 200, hint: 'How training fits your life, health, and community.', checks: [] },
|
| 222 |
],
|
| 223 |
},
|
| 224 |
];
|
|
|
|
| 466 |
<div>
|
| 467 |
<h1 className="page-title">Documents</h1>
|
| 468 |
<div className="page-sub">
|
| 469 |
+
Documents hub — program plans, check-ins, nutrition protocols, and progress boards. Drafts auto-save. Versions kept for rollback.
|
| 470 |
{projectList.length > 0 && ` · ${projectList.length} draft${projectList.length === 1 ? '' : 's'} in flight.`}
|
| 471 |
</div>
|
| 472 |
</div>
|
|
|
|
| 504 |
<div className="canvas-presets">
|
| 505 |
<div className="canvas-presets-head">
|
| 506 |
<div className="canvas-presets-title">{projectList.length > 0 ? 'Or start a new draft' : 'Pick a template'}</div>
|
| 507 |
+
<div className="canvas-presets-sub">13 templates · program plans, check-ins, posters, résumé, and more.</div>
|
| 508 |
</div>
|
| 509 |
<div className="canvas-presets-grid">
|
| 510 |
{TEMPLATES.map(t => (
|
|
|
|
| 877 |
<div className={`notion-page ${paperLike ? 'serif' : ''}`}>
|
| 878 |
<h1 className="notion-page-title">{project.name}</h1>
|
| 879 |
<div className="notion-page-meta">
|
| 880 |
+
{totalWords} words · ~{readingMinutes(totalWords)} min read · {template.sections.length} sections{paperLike ? ' · long-form doc' : ''}
|
| 881 |
</div>
|
| 882 |
{template.sections.map(s => {
|
| 883 |
const text = sections[s.id] || '';
|
|
@@ -147,7 +147,7 @@ function PdfPanel({ source, title, onClose }) {
|
|
| 147 |
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--canvas-text)', marginBottom: 6 }}>Compile failed</div>
|
| 148 |
{error}
|
| 149 |
<div style={{ marginTop: 10, color: 'var(--canvas-text-3)', fontSize: 11.5, fontFamily: 'var(--canvas-sans)' }}>
|
| 150 |
-
The free latex-online service can be flaky and doesn't support every package. The HTML preview to the left should still work for most
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
)}
|
|
|
|
| 147 |
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--canvas-text)', marginBottom: 6 }}>Compile failed</div>
|
| 148 |
{error}
|
| 149 |
<div style={{ marginTop: 10, color: 'var(--canvas-text-3)', fontSize: 11.5, fontFamily: 'var(--canvas-sans)' }}>
|
| 150 |
+
The free latex-online service can be flaky and doesn't support every package. The HTML preview to the left should still work for most long-form documents.
|
| 151 |
</div>
|
| 152 |
</div>
|
| 153 |
)}
|
|
@@ -720,7 +720,7 @@ export function GoalModal({ data, onClose }) {
|
|
| 720 |
<div className="modal-icon"><Icon name="bullseye" size={18}/></div>
|
| 721 |
<div style={{ flex: 1 }}>
|
| 722 |
<div className="modal-title">{editing ? 'Edit goal' : 'New goal'}</div>
|
| 723 |
-
<div className="modal-sub">
|
| 724 |
</div>
|
| 725 |
<button className="icon-btn" onClick={onClose}><Icon name="x" size={16}/></button>
|
| 726 |
</div>
|
|
|
|
| 720 |
<div className="modal-icon"><Icon name="bullseye" size={18}/></div>
|
| 721 |
<div style={{ flex: 1 }}>
|
| 722 |
<div className="modal-title">{editing ? 'Edit goal' : 'New goal'}</div>
|
| 723 |
+
<div className="modal-sub">Strength target or body-composition milestone.</div>
|
| 724 |
</div>
|
| 725 |
<button className="icon-btn" onClick={onClose}><Icon name="x" size={16}/></button>
|
| 726 |
</div>
|
|
@@ -2,18 +2,18 @@
|
|
| 2 |
import Icon from './CanvasIcon';
|
| 3 |
import { MOD } from './platform';
|
| 4 |
|
| 5 |
-
const TOUR_KEY = 'canvas-tour-seen-
|
| 6 |
|
| 7 |
const STEPS = [
|
| 8 |
{
|
| 9 |
-
title: 'Welcome to your
|
| 10 |
icon: 'shield',
|
| 11 |
-
body: 'This is your
|
| 12 |
},
|
| 13 |
{
|
| 14 |
title: 'Add widgets from the palette',
|
| 15 |
icon: 'plus',
|
| 16 |
-
body: `Click "Add widget" on the Workspace view, or hit ${MOD}+K and search. There are 30+ widgets
|
| 17 |
},
|
| 18 |
{
|
| 19 |
title: 'Make it yours',
|
|
@@ -23,7 +23,7 @@ const STEPS = [
|
|
| 23 |
{
|
| 24 |
title: 'Try the challenge widgets',
|
| 25 |
icon: 'gavel',
|
| 26 |
-
body: 'Reviewer 2, Devil\'s Advocate, and Scope Realism are tuned to push back, not validate. They\'re where
|
| 27 |
},
|
| 28 |
];
|
| 29 |
|
|
|
|
| 2 |
import Icon from './CanvasIcon';
|
| 3 |
import { MOD } from './platform';
|
| 4 |
|
| 5 |
+
const TOUR_KEY = 'canvas-tour-seen-musclegrowth-v1';
|
| 6 |
|
| 7 |
const STEPS = [
|
| 8 |
{
|
| 9 |
+
title: 'Welcome to your Fitness Canvas',
|
| 10 |
icon: 'shield',
|
| 11 |
+
body: 'This is your training workspace. Two views — Insights (highlights from your chats) and Workspace (a customizable dashboard of widgets). It starts empty so you can build it the way you want.',
|
| 12 |
},
|
| 13 |
{
|
| 14 |
title: 'Add widgets from the palette',
|
| 15 |
icon: 'plus',
|
| 16 |
+
body: `Click "Add widget" on the Workspace view, or hit ${MOD}+K and search. There are 30+ widgets — workouts, deadlines, tracking, notes, plus challenge widgets that push back on weak plans.`,
|
| 17 |
},
|
| 18 |
{
|
| 19 |
title: 'Make it yours',
|
|
|
|
| 23 |
{
|
| 24 |
title: 'Try the challenge widgets',
|
| 25 |
icon: 'gavel',
|
| 26 |
+
body: 'Reviewer 2, Devil\'s Advocate, and Scope Realism are tuned to push back, not validate. They\'re where your program gets sharpened. Add them last — when you\'re ready for honest feedback.',
|
| 27 |
},
|
| 28 |
];
|
| 29 |
|
|
@@ -837,7 +837,7 @@ export function HabitsWidget({ state, setState, openModal }) {
|
|
| 837 |
);
|
| 838 |
})}
|
| 839 |
{habits.length === 0 && (
|
| 840 |
-
<EmptyState icon="flame" title="No habits yet" hint="Track daily
|
| 841 |
)}
|
| 842 |
</div>
|
| 843 |
<button className="add-tiny" onClick={add}>+ New habit</button>
|
|
@@ -876,7 +876,7 @@ export function GoalsWidget({ state, setState, openModal }) {
|
|
| 876 |
</div>
|
| 877 |
))}
|
| 878 |
{goals.length === 0 && (
|
| 879 |
-
<EmptyState icon="bullseye" title="No goals yet" hint="
|
| 880 |
)}
|
| 881 |
</div>
|
| 882 |
<button className="add-tiny" onClick={add}>+ New goal</button>
|
|
@@ -1439,23 +1439,23 @@ export function DocumenterWidget({ state, setState }) {
|
|
| 1439 |
);
|
| 1440 |
}
|
| 1441 |
|
| 1442 |
-
// =====
|
| 1443 |
-
// Captures the
|
| 1444 |
-
//
|
| 1445 |
-
// (open/in-progress/completed) and an inline note.
|
| 1446 |
const PHD_MILESTONES = [
|
| 1447 |
-
{ id: 'courses', label: '
|
| 1448 |
-
{ id: 'prelim', label: '
|
| 1449 |
-
{ id: 'lit-review', label: '
|
| 1450 |
-
{ id: 'topic', label: 'Pick
|
| 1451 |
-
{ id: 'committee', label: '
|
| 1452 |
-
{ id: 'irb', label: '
|
| 1453 |
-
{ id: 'data', label: '
|
| 1454 |
-
{ id: 'comps', label: '
|
| 1455 |
-
{ id: 'analysis', label: '
|
| 1456 |
-
{ id: 'writing', label: '
|
| 1457 |
-
{ id: 'defense', label: '
|
| 1458 |
-
{ id: 'proquest', label: '
|
| 1459 |
];
|
| 1460 |
|
| 1461 |
export function PhdJourneyWidget({ state, setState }) {
|
|
@@ -1527,33 +1527,33 @@ export function PhdJourneyWidget({ state, setState }) {
|
|
| 1527 |
);
|
| 1528 |
}
|
| 1529 |
|
| 1530 |
-
// =====
|
| 1531 |
-
// Static curated list of useful
|
| 1532 |
const PHD_RESOURCE_GROUPS = [
|
| 1533 |
{
|
| 1534 |
-
label: '
|
| 1535 |
items: [
|
| 1536 |
-
{ name: '
|
| 1537 |
-
{ name: '
|
| 1538 |
-
{ name: '
|
| 1539 |
-
{ name: '
|
| 1540 |
-
{ name: '
|
| 1541 |
],
|
| 1542 |
},
|
| 1543 |
{
|
| 1544 |
-
label: '
|
| 1545 |
items: [
|
| 1546 |
-
{ name: '
|
| 1547 |
-
{ name: '
|
| 1548 |
-
{ name: '
|
| 1549 |
],
|
| 1550 |
},
|
| 1551 |
{
|
| 1552 |
-
label: '
|
| 1553 |
items: [
|
| 1554 |
-
{ name: '
|
| 1555 |
-
{ name: '
|
| 1556 |
-
{ name: '
|
| 1557 |
],
|
| 1558 |
},
|
| 1559 |
];
|
|
@@ -1629,7 +1629,7 @@ const STUB_PLANS = {
|
|
| 1629 |
'outline': ['Collapsible tree', 'Drop Insights into slots', 'Promote to Deliverable section'],
|
| 1630 |
'latex': ['Render math as you type', 'Snippet library', 'Copy as image / TeX'],
|
| 1631 |
'draft-locker': ['Versioned chapter drafts', 'Diff between versions', 'Roll back any change'],
|
| 1632 |
-
'gantt': ['
|
| 1633 |
'mood': ['Daily slider', 'Trend graph', 'Correlate with productive days'],
|
| 1634 |
'sleep': ['Sleep duration vs. word output', 'Energy heatmap', 'Apple Health import'],
|
| 1635 |
'focus': ['Curated ambient playlists', 'Focus session timer', 'Auto-pause on Pomodoro break'],
|
|
@@ -1640,7 +1640,7 @@ const STUB_PLANS = {
|
|
| 1640 |
'datasets': ['Public datasets by domain', 'Saved searches', 'License + access notes'],
|
| 1641 |
'methods': ['When to use what test', 'Examples + citations', 'Saved templates per chapter'],
|
| 1642 |
'discounts': ['Software & services with edu pricing', 'Discount expiration tracking'],
|
| 1643 |
-
'assumption': ['Names hidden assumptions', 'Asks "what if wrong?"', 'Logs to
|
| 1644 |
'whats-missing': ['Gap analysis on lit review', 'Compares to top venues', 'Suggests targeted reads'],
|
| 1645 |
'calibrator': ['Challenges every "results show" claim', 'Asks for the prior', 'Flags p-hacking patterns'],
|
| 1646 |
};
|
|
|
|
| 837 |
);
|
| 838 |
})}
|
| 839 |
{habits.length === 0 && (
|
| 840 |
+
<EmptyState icon="flame" title="No habits yet" hint="Track daily training habits. Hit protein target, 8k steps, 10 min mobility."/>
|
| 841 |
)}
|
| 842 |
</div>
|
| 843 |
<button className="add-tiny" onClick={add}>+ New habit</button>
|
|
|
|
| 876 |
</div>
|
| 877 |
))}
|
| 878 |
{goals.length === 0 && (
|
| 879 |
+
<EmptyState icon="bullseye" title="No goals yet" hint="Strength targets, body-composition goals, anything you want to track."/>
|
| 880 |
)}
|
| 881 |
</div>
|
| 882 |
<button className="add-tiny" onClick={add}>+ New goal</button>
|
|
|
|
| 1439 |
);
|
| 1440 |
}
|
| 1441 |
|
| 1442 |
+
// ===== Training Roadmap — standard milestones with status + notes =====
|
| 1443 |
+
// Captures the training journey: assessment, base building, technique, program
|
| 1444 |
+
// design, nutrition, progression, peak, and maintenance. Each milestone has a
|
| 1445 |
+
// status (open/in-progress/completed) and an inline note.
|
| 1446 |
const PHD_MILESTONES = [
|
| 1447 |
+
{ id: 'courses', label: 'Baseline assessment & goal setting', hint: 'Record current lifts, measurements, and a clear goal.' },
|
| 1448 |
+
{ id: 'prelim', label: 'Movement screen', hint: 'Check mobility and any injury history before loading.' },
|
| 1449 |
+
{ id: 'lit-review', label: 'Learn the fundamentals', hint: 'Progressive overload, rep ranges, and form basics.' },
|
| 1450 |
+
{ id: 'topic', label: 'Pick a training split', hint: 'Full-body, upper/lower, or PPL — match it to your schedule.' },
|
| 1451 |
+
{ id: 'committee', label: 'Set up your gym & equipment', hint: 'Home vs. commercial: trade-offs in equipment and cost.' },
|
| 1452 |
+
{ id: 'irb', label: 'Dial in nutrition', hint: 'Set protein and calorie targets for your goal.' },
|
| 1453 |
+
{ id: 'data', label: 'Start logging workouts', hint: 'Track every session. Pilot the plan for 2 weeks first.' },
|
| 1454 |
+
{ id: 'comps', label: 'First progress check', hint: 'Reassess lifts and measurements at week 4–6.' },
|
| 1455 |
+
{ id: 'analysis', label: 'Adjust volume & progression', hint: 'Add sets or load where progress has stalled.' },
|
| 1456 |
+
{ id: 'writing', label: 'Run the hypertrophy block', hint: 'One block at a time. Aim for consistent progressive overload.' },
|
| 1457 |
+
{ id: 'defense', label: 'Deload & peak', hint: 'Plan a deload every 5–6 weeks. Use the Deload template.' },
|
| 1458 |
+
{ id: 'proquest', label: 'Transition to maintenance', hint: 'Lock in gains before starting the next block.' },
|
| 1459 |
];
|
| 1460 |
|
| 1461 |
export function PhdJourneyWidget({ state, setState }) {
|
|
|
|
| 1527 |
);
|
| 1528 |
}
|
| 1529 |
|
| 1530 |
+
// ===== Fitness Resources — curated links + apps =====
|
| 1531 |
+
// Static curated list of useful training/nutrition tools, plus user-added links.
|
| 1532 |
const PHD_RESOURCE_GROUPS = [
|
| 1533 |
{
|
| 1534 |
+
label: 'Training & tracking apps',
|
| 1535 |
items: [
|
| 1536 |
+
{ name: 'Boostcamp', href: 'https://www.boostcamp.app/', desc: 'Free proven programs + workout logging' },
|
| 1537 |
+
{ name: 'Strong', href: 'https://www.strong.app/', desc: 'Simple, popular workout logger' },
|
| 1538 |
+
{ name: 'Hevy', href: 'https://www.hevyapp.com/', desc: 'Workout tracker with routines and history' },
|
| 1539 |
+
{ name: 'Liftosaur', href: 'https://www.liftosaur.com/', desc: 'Scriptable, open-source workout planner' },
|
| 1540 |
+
{ name: 'ExRx.net', href: 'https://exrx.net/', desc: 'Exercise library with form instructions' },
|
| 1541 |
],
|
| 1542 |
},
|
| 1543 |
{
|
| 1544 |
+
label: 'Nutrition & recovery',
|
| 1545 |
items: [
|
| 1546 |
+
{ name: 'Cronometer', href: 'https://cronometer.com/', desc: 'Detailed calorie and micronutrient tracker' },
|
| 1547 |
+
{ name: 'MyFitnessPal', href: 'https://www.myfitnesspal.com/', desc: 'Large food database for macro tracking' },
|
| 1548 |
+
{ name: 'TDEE Calculator', href: 'https://tdeecalculator.net/', desc: 'Estimate maintenance calories fast' },
|
| 1549 |
],
|
| 1550 |
},
|
| 1551 |
{
|
| 1552 |
+
label: 'Learn & community',
|
| 1553 |
items: [
|
| 1554 |
+
{ name: 'Stronger by Science', href: 'https://www.strongerbyscience.com/', desc: 'Evidence-based training and nutrition articles' },
|
| 1555 |
+
{ name: 'r/Fitness Wiki', href: 'https://thefitness.wiki/', desc: 'Beginner-friendly guides and program picks' },
|
| 1556 |
+
{ name: 'Jeff Nippard (YouTube)', href: 'https://www.youtube.com/@JeffNippard', desc: 'Science-based hypertrophy breakdowns' },
|
| 1557 |
],
|
| 1558 |
},
|
| 1559 |
];
|
|
|
|
| 1629 |
'outline': ['Collapsible tree', 'Drop Insights into slots', 'Promote to Deliverable section'],
|
| 1630 |
'latex': ['Render math as you type', 'Snippet library', 'Copy as image / TeX'],
|
| 1631 |
'draft-locker': ['Versioned chapter drafts', 'Diff between versions', 'Roll back any change'],
|
| 1632 |
+
'gantt': ['Base → hypertrophy → peak timeline', 'Critical-path highlighting', 'Drag to reschedule'],
|
| 1633 |
'mood': ['Daily slider', 'Trend graph', 'Correlate with productive days'],
|
| 1634 |
'sleep': ['Sleep duration vs. word output', 'Energy heatmap', 'Apple Health import'],
|
| 1635 |
'focus': ['Curated ambient playlists', 'Focus session timer', 'Auto-pause on Pomodoro break'],
|
|
|
|
| 1640 |
'datasets': ['Public datasets by domain', 'Saved searches', 'License + access notes'],
|
| 1641 |
'methods': ['When to use what test', 'Examples + citations', 'Saved templates per chapter'],
|
| 1642 |
'discounts': ['Software & services with edu pricing', 'Discount expiration tracking'],
|
| 1643 |
+
'assumption': ['Names hidden assumptions', 'Asks "what if wrong?"', 'Logs to an assumptions list'],
|
| 1644 |
'whats-missing': ['Gap analysis on lit review', 'Compares to top venues', 'Suggests targeted reads'],
|
| 1645 |
'calibrator': ['Challenges every "results show" claim', 'Asks for the prior', 'Flags p-hacking patterns'],
|
| 1646 |
};
|
|
@@ -1,8 +1,8 @@
|
|
| 1 |
-
// Demo data for a
|
| 2 |
|
| 3 |
export const DEMO_PROJECT = {
|
| 4 |
-
title: "
|
| 5 |
-
meta: "
|
| 6 |
};
|
| 7 |
|
| 8 |
export const INSIGHTS = [
|
|
@@ -12,75 +12,75 @@ export const INSIGHTS = [
|
|
| 12 |
icon: 'graph',
|
| 13 |
category: 'progress',
|
| 14 |
confidence: 82,
|
| 15 |
-
summary: '
|
| 16 |
bullets: [
|
| 17 |
-
'
|
| 18 |
-
'
|
| 19 |
-
'<strong>Risk:</strong>
|
| 20 |
],
|
| 21 |
pinned: true,
|
| 22 |
sources: 18,
|
| 23 |
updatedMinutesAgo: 5,
|
| 24 |
quotes: [
|
| 25 |
-
'"
|
| 26 |
-
'"
|
| 27 |
],
|
| 28 |
},
|
| 29 |
{
|
| 30 |
id: 'i-method',
|
| 31 |
-
title: '
|
| 32 |
icon: 'flask',
|
| 33 |
category: 'theory',
|
| 34 |
confidence: 71,
|
| 35 |
-
summary: '
|
| 36 |
bullets: [
|
| 37 |
-
'
|
| 38 |
-
'
|
| 39 |
-
'
|
| 40 |
],
|
| 41 |
sources: 14,
|
| 42 |
updatedMinutesAgo: 14,
|
| 43 |
quotes: [
|
| 44 |
-
'"
|
| 45 |
-
'"
|
| 46 |
],
|
| 47 |
},
|
| 48 |
{
|
| 49 |
id: 'i-lit',
|
| 50 |
-
title: '
|
| 51 |
icon: 'book',
|
| 52 |
category: 'literature',
|
| 53 |
confidence: 76,
|
| 54 |
-
summary: '
|
| 55 |
bullets: [
|
| 56 |
-
'<strong>Coverage:</strong>
|
| 57 |
-
'<strong>Gap:</strong>
|
| 58 |
-
'<strong>Gap:</strong> no
|
| 59 |
],
|
| 60 |
sources: 32,
|
| 61 |
updatedMinutesAgo: 28,
|
| 62 |
quotes: [
|
| 63 |
-
'"
|
| 64 |
-
'"
|
| 65 |
],
|
| 66 |
},
|
| 67 |
{
|
| 68 |
id: 'i-questions',
|
| 69 |
-
title: 'Open
|
| 70 |
icon: 'sparkles',
|
| 71 |
category: 'theory',
|
| 72 |
confidence: 63,
|
| 73 |
-
summary: 'Three live threads. Q1 (
|
| 74 |
bullets: [
|
| 75 |
-
'<strong>Q1:</strong>
|
| 76 |
-
'<strong>Q2:</strong>
|
| 77 |
-
'<strong>Q3:</strong>
|
| 78 |
],
|
| 79 |
sources: 9,
|
| 80 |
updatedMinutesAgo: 41,
|
| 81 |
quotes: [
|
| 82 |
-
'"
|
| 83 |
-
'"
|
| 84 |
],
|
| 85 |
},
|
| 86 |
{
|
|
@@ -89,18 +89,18 @@ export const INSIGHTS = [
|
|
| 89 |
icon: 'arrow',
|
| 90 |
category: 'action',
|
| 91 |
confidence: 85,
|
| 92 |
-
summary: 'Near-term actions tied to
|
| 93 |
bullets: [
|
| 94 |
-
'
|
| 95 |
-
'
|
| 96 |
-
'
|
| 97 |
-
'
|
| 98 |
],
|
| 99 |
sources: 7,
|
| 100 |
updatedMinutesAgo: 9,
|
| 101 |
quotes: [
|
| 102 |
-
'"
|
| 103 |
-
'"
|
| 104 |
],
|
| 105 |
},
|
| 106 |
{
|
|
@@ -109,16 +109,16 @@ export const INSIGHTS = [
|
|
| 109 |
icon: 'alert',
|
| 110 |
category: 'risk',
|
| 111 |
confidence: 74,
|
| 112 |
-
summary: 'One
|
| 113 |
bullets: [
|
| 114 |
-
'<strong>
|
| 115 |
-
'<strong>
|
| 116 |
],
|
| 117 |
sources: 6,
|
| 118 |
updatedMinutesAgo: 20,
|
| 119 |
quotes: [
|
| 120 |
-
'"Without
|
| 121 |
-
'"
|
| 122 |
],
|
| 123 |
},
|
| 124 |
];
|
|
@@ -139,14 +139,14 @@ export const WIDGET_CATALOG = [
|
|
| 139 |
{ type: 'kanban', name: 'Task Board', desc: 'Drag-to-move with priority filter chips and due-date sort', icon: 'kanban', cat: 'project', defaultSize: 'L', enhanced: true },
|
| 140 |
{ type: 'deadlines', name: 'Deadlines', desc: 'Countdown plus per-deadline .ics calendar export', icon: 'calendar', cat: 'project', defaultSize: 'S', enhanced: true },
|
| 141 |
{ type: 'pomodoro', name: 'Pomodoro', desc: 'Real timer with break cycle and session counter', icon: 'timer', cat: 'project', defaultSize: 'S', enhanced: true },
|
| 142 |
-
{ type: 'gantt', name: 'Milestone Timeline', desc: '
|
| 143 |
{ type: 'meeting-log', name: 'Meeting Log', desc: 'Per-stakeholder, last contact, actions', icon: 'message', cat: 'project', defaultSize: 'M' },
|
| 144 |
{ type: 'goals', name: 'Goals / OKRs', desc: 'Quarterly milestones with progress sliders', icon: 'bullseye', cat: 'project', defaultSize: 'M' },
|
| 145 |
{ type: 'calendar', name: 'Calendar', desc: 'Month grid with deadlines and writing days', icon: 'calendar', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 146 |
{ type: 'activity', name: 'Activity Feed', desc: 'Chronological log of edits across widgets', icon: 'graph', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 147 |
{ type: 'documenter', name: 'Daily Documenter', desc: 'Date-stamped journal · AI weekly summary (LLM stub)', icon: 'pencil', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 148 |
-
{ type: 'phd-journey', name: '
|
| 149 |
-
{ type: 'phd-resources', name: '
|
| 150 |
|
| 151 |
{ type: 'mood', name: 'Mood / Burnout Check-in', desc: 'Daily slider, trend graph', icon: 'smile', cat: 'wellness', defaultSize: 'S', stub: true },
|
| 152 |
{ type: 'sleep', name: 'Sleep & Energy', desc: 'Correlate with productive days', icon: 'heart', cat: 'wellness', defaultSize: 'S', stub: true },
|
|
@@ -164,21 +164,21 @@ export const WIDGET_CATALOG = [
|
|
| 164 |
{ type: 'budget', name: 'Budget Tracker', desc: 'Research spend vs. cap', icon: 'wallet', cat: 'practical', defaultSize: 'S' },
|
| 165 |
{ type: 'discounts', name: 'Student Discounts', desc: 'Software & services with edu pricing', icon: 'star', cat: 'practical', defaultSize: 'S', stub: true },
|
| 166 |
|
| 167 |
-
{ type: 'reviewer-2', name: 'Reviewer 2 Simulator', desc: 'Paste a
|
| 168 |
-
{ type: 'devils-advocate', name: 'Devil\'s Advocate', desc: 'Strongest counter-arguments to your
|
| 169 |
{ type: 'scope-realism', name: 'Scope Realism Check', desc: 'Brutal feasibility verdict given timeline', icon: 'bullseye', cat: 'critic', defaultSize: 'M', critic: true },
|
| 170 |
{ type: 'assumption', name: 'Assumption Excavator', desc: 'Names hidden assumptions, asks "what if wrong?"', icon: 'brain', cat: 'critic', defaultSize: 'M', critic: true, stub: true },
|
| 171 |
-
{ type: 'whats-missing', name: '"What\'s Missing"', desc: 'Gap analysis on
|
| 172 |
-
{ type: 'calibrator', name: 'Confidence Calibrator', desc: 'Challenges every "
|
| 173 |
];
|
| 174 |
|
| 175 |
export const CATEGORIES = [
|
| 176 |
{ id: 'all', label: 'All' },
|
| 177 |
-
{ id: 'research', label: '
|
| 178 |
-
{ id: 'writing', label: '
|
| 179 |
{ id: 'project', label: 'Project' },
|
| 180 |
{ id: 'wellness', label: 'Wellness' },
|
| 181 |
-
{ id: 'career', label: '
|
| 182 |
{ id: 'data', label: 'Data' },
|
| 183 |
{ id: 'practical', label: 'Practical' },
|
| 184 |
{ id: 'critic', label: 'Anti-yes-man', critic: true },
|
|
@@ -193,8 +193,8 @@ const presetIds = (types) => types.map((t, i) => ({ id: `pre-${t.type}-${i}`, ..
|
|
| 193 |
export const WORKSPACE_PRESETS = [
|
| 194 |
{
|
| 195 |
id: 'day1-soc',
|
| 196 |
-
name: '
|
| 197 |
-
desc: 'Get oriented: reading queue, notes, deadlines,
|
| 198 |
icon: 'sparkles',
|
| 199 |
layout: presetIds([
|
| 200 |
{ type: 'reading-queue', size: 'M' },
|
|
@@ -207,8 +207,8 @@ export const WORKSPACE_PRESETS = [
|
|
| 207 |
},
|
| 208 |
{
|
| 209 |
id: 'writing-sprint',
|
| 210 |
-
name: '
|
| 211 |
-
desc: '
|
| 212 |
icon: 'pencil',
|
| 213 |
layout: presetIds([
|
| 214 |
{ type: 'writing', size: 'M' },
|
|
@@ -221,8 +221,8 @@ export const WORKSPACE_PRESETS = [
|
|
| 221 |
},
|
| 222 |
{
|
| 223 |
id: 'audit-prep',
|
| 224 |
-
name: '
|
| 225 |
-
desc: '
|
| 226 |
icon: 'book',
|
| 227 |
layout: presetIds([
|
| 228 |
{ type: 'bibliography', size: 'L' },
|
|
@@ -234,8 +234,8 @@ export const WORKSPACE_PRESETS = [
|
|
| 234 |
},
|
| 235 |
{
|
| 236 |
id: 'incident-mode',
|
| 237 |
-
name: '
|
| 238 |
-
desc: '
|
| 239 |
icon: 'gavel',
|
| 240 |
layout: presetIds([
|
| 241 |
{ type: 'writing', size: 'M' },
|
|
@@ -291,7 +291,7 @@ export const EMPTY_STATE = {
|
|
| 291 |
documenter: { entries: [], lastSummary: null },
|
| 292 |
'phd-journey': {
|
| 293 |
// Status per milestone: 'open' | 'in-progress' | 'completed'
|
| 294 |
-
// Milestones come from the standard
|
| 295 |
statuses: {},
|
| 296 |
notes: {},
|
| 297 |
},
|
|
|
|
| 1 |
+
// Demo data for a lifter running a 12-week hypertrophy block.
|
| 2 |
|
| 3 |
export const DEMO_PROJECT = {
|
| 4 |
+
title: "12-Week Hypertrophy Block — Upper/Lower",
|
| 5 |
+
meta: "Intermediate lifter · muscle gain phase",
|
| 6 |
};
|
| 7 |
|
| 8 |
export const INSIGHTS = [
|
|
|
|
| 12 |
icon: 'graph',
|
| 13 |
category: 'progress',
|
| 14 |
confidence: 82,
|
| 15 |
+
summary: 'Week 6 of a 12-week upper/lower block. Upper-body volume and pressing strength are trending up; leg volume and step count are the main gaps before the next deload.',
|
| 16 |
bullets: [
|
| 17 |
+
'Strength: <strong>bench +7.5kg</strong> · overhead press +5kg since week 1',
|
| 18 |
+
'Volume: <strong>16 sets/week</strong> chest & back · legs lagging at 10',
|
| 19 |
+
'<strong>Risk:</strong> right shoulder tightness on incline pressing',
|
| 20 |
],
|
| 21 |
pinned: true,
|
| 22 |
sources: 18,
|
| 23 |
updatedMinutesAgo: 5,
|
| 24 |
quotes: [
|
| 25 |
+
'"Add a second lower day before increasing upper volume." — Program Planner chat',
|
| 26 |
+
'"Warm up the rotator cuff before pressing." — Form & Safety Coach',
|
| 27 |
],
|
| 28 |
},
|
| 29 |
{
|
| 30 |
id: 'i-method',
|
| 31 |
+
title: 'Nutrition & recovery',
|
| 32 |
icon: 'flask',
|
| 33 |
category: 'theory',
|
| 34 |
confidence: 71,
|
| 35 |
+
summary: 'Protein target is being hit most days; total calories are slightly under the surplus goal. Sleep is the biggest recovery limiter this block.',
|
| 36 |
bullets: [
|
| 37 |
+
'Protein: <strong>1.9 g/kg</strong> avg · target 2.0–2.2 g/kg',
|
| 38 |
+
'Calories: <strong>~250 kcal under</strong> the lean-bulk surplus',
|
| 39 |
+
'Sleep: <strong>6.2 h avg</strong> — recovery and gym energy suffering',
|
| 40 |
],
|
| 41 |
sources: 14,
|
| 42 |
updatedMinutesAgo: 14,
|
| 43 |
quotes: [
|
| 44 |
+
'"Add a shake + carbs post-workout to close the calorie gap." — Nutrition Strategist',
|
| 45 |
+
'"Prioritize 7.5h sleep before adding volume." — Recovery Specialist',
|
| 46 |
],
|
| 47 |
},
|
| 48 |
{
|
| 49 |
id: 'i-lit',
|
| 50 |
+
title: 'Training knowledge',
|
| 51 |
icon: 'book',
|
| 52 |
category: 'literature',
|
| 53 |
confidence: 76,
|
| 54 |
+
summary: 'Solid grasp of progressive overload and rep ranges for chest and back. Weaker on leg-day exercise selection and how to program a deload.',
|
| 55 |
bullets: [
|
| 56 |
+
'<strong>Coverage:</strong> hypertrophy rep ranges & progressive overload',
|
| 57 |
+
'<strong>Gap:</strong> quad vs. hip-hinge <strong>exercise selection</strong> for legs',
|
| 58 |
+
'<strong>Gap:</strong> no clear <strong>deload</strong> protocol planned',
|
| 59 |
],
|
| 60 |
sources: 32,
|
| 61 |
updatedMinutesAgo: 28,
|
| 62 |
quotes: [
|
| 63 |
+
'"Rotate hack squats and RDLs to balance quads and hamstrings." — Hypertrophy Coach',
|
| 64 |
+
'"Plan a deload every 5–6 weeks." — Program Planner',
|
| 65 |
],
|
| 66 |
},
|
| 67 |
{
|
| 68 |
id: 'i-questions',
|
| 69 |
+
title: 'Open training questions',
|
| 70 |
icon: 'sparkles',
|
| 71 |
category: 'theory',
|
| 72 |
confidence: 63,
|
| 73 |
+
summary: 'Three live threads. Q1 (adding a third training day for legs) gates the next block. Q2–Q3 affect nutrition and recovery choices.',
|
| 74 |
bullets: [
|
| 75 |
+
'<strong>Q1:</strong> Move to a 4-day upper/lower or keep 3 full-body days?',
|
| 76 |
+
'<strong>Q2:</strong> Lean-bulk surplus size — 200 or 400 kcal over maintenance?',
|
| 77 |
+
'<strong>Q3:</strong> Cardio on rest days without hurting recovery?',
|
| 78 |
],
|
| 79 |
sources: 9,
|
| 80 |
updatedMinutesAgo: 41,
|
| 81 |
quotes: [
|
| 82 |
+
'"A 4th day lets you bring legs up without cutting upper volume." — Program Planner',
|
| 83 |
+
'"Keep the surplus small to limit fat gain." — Nutrition Strategist',
|
| 84 |
],
|
| 85 |
},
|
| 86 |
{
|
|
|
|
| 89 |
icon: 'arrow',
|
| 90 |
category: 'action',
|
| 91 |
confidence: 85,
|
| 92 |
+
summary: 'Near-term actions tied to the next training week and the upcoming deload. Two items have slipped a week.',
|
| 93 |
bullets: [
|
| 94 |
+
'Add a <strong>second lower-body day</strong> and rebalance leg volume',
|
| 95 |
+
'Hit <strong>2.0 g/kg protein</strong> daily and log every meal for a week',
|
| 96 |
+
'Add <strong>10 min mobility</strong> pre-lift for the shoulder',
|
| 97 |
+
'Schedule a <strong>deload</strong> for week 7',
|
| 98 |
],
|
| 99 |
sources: 7,
|
| 100 |
updatedMinutesAgo: 9,
|
| 101 |
quotes: [
|
| 102 |
+
'"Under-recovered legs are the #1 progress blocker right now." — Recovery Specialist',
|
| 103 |
+
'"Track protein before adding training volume." — Nutrition Strategist',
|
| 104 |
],
|
| 105 |
},
|
| 106 |
{
|
|
|
|
| 109 |
icon: 'alert',
|
| 110 |
category: 'risk',
|
| 111 |
confidence: 74,
|
| 112 |
+
summary: 'One physical limiter (shoulder tightness), one lifestyle limiter (sleep). Sleep is the higher risk to progress this block.',
|
| 113 |
bullets: [
|
| 114 |
+
'<strong>Physical:</strong> shoulder tightness limits incline pressing volume',
|
| 115 |
+
'<strong>Lifestyle:</strong> sleep under 6.5h is capping recovery and gym energy',
|
| 116 |
],
|
| 117 |
sources: 6,
|
| 118 |
updatedMinutesAgo: 20,
|
| 119 |
quotes: [
|
| 120 |
+
'"Without recovery you cannot express the strength you are building." — Recovery Specialist',
|
| 121 |
+
'"Fix sleep before chasing bigger lifts." — Form & Safety Coach',
|
| 122 |
],
|
| 123 |
},
|
| 124 |
];
|
|
|
|
| 139 |
{ type: 'kanban', name: 'Task Board', desc: 'Drag-to-move with priority filter chips and due-date sort', icon: 'kanban', cat: 'project', defaultSize: 'L', enhanced: true },
|
| 140 |
{ type: 'deadlines', name: 'Deadlines', desc: 'Countdown plus per-deadline .ics calendar export', icon: 'calendar', cat: 'project', defaultSize: 'S', enhanced: true },
|
| 141 |
{ type: 'pomodoro', name: 'Pomodoro', desc: 'Real timer with break cycle and session counter', icon: 'timer', cat: 'project', defaultSize: 'S', enhanced: true },
|
| 142 |
+
{ type: 'gantt', name: 'Milestone Timeline', desc: 'Base building → hypertrophy → peak', icon: 'flag', cat: 'project', defaultSize: 'L', stub: true },
|
| 143 |
{ type: 'meeting-log', name: 'Meeting Log', desc: 'Per-stakeholder, last contact, actions', icon: 'message', cat: 'project', defaultSize: 'M' },
|
| 144 |
{ type: 'goals', name: 'Goals / OKRs', desc: 'Quarterly milestones with progress sliders', icon: 'bullseye', cat: 'project', defaultSize: 'M' },
|
| 145 |
{ type: 'calendar', name: 'Calendar', desc: 'Month grid with deadlines and writing days', icon: 'calendar', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 146 |
{ type: 'activity', name: 'Activity Feed', desc: 'Chronological log of edits across widgets', icon: 'graph', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 147 |
{ type: 'documenter', name: 'Daily Documenter', desc: 'Date-stamped journal · AI weekly summary (LLM stub)', icon: 'pencil', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 148 |
+
{ type: 'phd-journey', name: 'Training Roadmap', desc: 'Milestones from assessment → base building → hypertrophy → peak → maintain', icon: 'flag', cat: 'project', defaultSize: 'M', enhanced: true },
|
| 149 |
+
{ type: 'phd-resources', name: 'Fitness Resources', desc: 'Programs, exercise libraries, nutrition guides, and community references', icon: 'star', cat: 'research', defaultSize: 'M', enhanced: true },
|
| 150 |
|
| 151 |
{ type: 'mood', name: 'Mood / Burnout Check-in', desc: 'Daily slider, trend graph', icon: 'smile', cat: 'wellness', defaultSize: 'S', stub: true },
|
| 152 |
{ type: 'sleep', name: 'Sleep & Energy', desc: 'Correlate with productive days', icon: 'heart', cat: 'wellness', defaultSize: 'S', stub: true },
|
|
|
|
| 164 |
{ type: 'budget', name: 'Budget Tracker', desc: 'Research spend vs. cap', icon: 'wallet', cat: 'practical', defaultSize: 'S' },
|
| 165 |
{ type: 'discounts', name: 'Student Discounts', desc: 'Software & services with edu pricing', icon: 'star', cat: 'practical', defaultSize: 'S', stub: true },
|
| 166 |
|
| 167 |
+
{ type: 'reviewer-2', name: 'Reviewer 2 Simulator', desc: 'Paste a plan → harsh, honest critique', icon: 'gavel', cat: 'critic', defaultSize: 'M', critic: true },
|
| 168 |
+
{ type: 'devils-advocate', name: 'Devil\'s Advocate', desc: 'Strongest counter-arguments to your plan', icon: 'scale', cat: 'critic', defaultSize: 'M', critic: true },
|
| 169 |
{ type: 'scope-realism', name: 'Scope Realism Check', desc: 'Brutal feasibility verdict given timeline', icon: 'bullseye', cat: 'critic', defaultSize: 'M', critic: true },
|
| 170 |
{ type: 'assumption', name: 'Assumption Excavator', desc: 'Names hidden assumptions, asks "what if wrong?"', icon: 'brain', cat: 'critic', defaultSize: 'M', critic: true, stub: true },
|
| 171 |
+
{ type: 'whats-missing', name: '"What\'s Missing"', desc: 'Gap analysis on your program or diet', icon: 'alert', cat: 'critic', defaultSize: 'S', critic: true, stub: true },
|
| 172 |
+
{ type: 'calibrator', name: 'Confidence Calibrator', desc: 'Challenges every "this always works" claim', icon: 'scale', cat: 'critic', defaultSize: 'S', critic: true, stub: true },
|
| 173 |
];
|
| 174 |
|
| 175 |
export const CATEGORIES = [
|
| 176 |
{ id: 'all', label: 'All' },
|
| 177 |
+
{ id: 'research', label: 'Training' },
|
| 178 |
+
{ id: 'writing', label: 'Logging' },
|
| 179 |
{ id: 'project', label: 'Project' },
|
| 180 |
{ id: 'wellness', label: 'Wellness' },
|
| 181 |
+
{ id: 'career', label: 'Goals' },
|
| 182 |
{ id: 'data', label: 'Data' },
|
| 183 |
{ id: 'practical', label: 'Practical' },
|
| 184 |
{ id: 'critic', label: 'Anti-yes-man', critic: true },
|
|
|
|
| 193 |
export const WORKSPACE_PRESETS = [
|
| 194 |
{
|
| 195 |
id: 'day1-soc',
|
| 196 |
+
name: 'Beginner Starter',
|
| 197 |
+
desc: 'Get oriented: reading queue, notes, deadlines, task board, pomodoro.',
|
| 198 |
icon: 'sparkles',
|
| 199 |
layout: presetIds([
|
| 200 |
{ type: 'reading-queue', size: 'M' },
|
|
|
|
| 207 |
},
|
| 208 |
{
|
| 209 |
id: 'writing-sprint',
|
| 210 |
+
name: 'Program Builder',
|
| 211 |
+
desc: 'Plan your split: writing pad, outline, highlights, task board, pomodoro.',
|
| 212 |
icon: 'pencil',
|
| 213 |
layout: presetIds([
|
| 214 |
{ type: 'writing', size: 'M' },
|
|
|
|
| 221 |
},
|
| 222 |
{
|
| 223 |
id: 'audit-prep',
|
| 224 |
+
name: 'Cut Prep',
|
| 225 |
+
desc: 'Dial in nutrition and tracking: notes, reading queue, highlights, task board.',
|
| 226 |
icon: 'book',
|
| 227 |
layout: presetIds([
|
| 228 |
{ type: 'bibliography', size: 'L' },
|
|
|
|
| 234 |
},
|
| 235 |
{
|
| 236 |
id: 'incident-mode',
|
| 237 |
+
name: 'Plateau Buster',
|
| 238 |
+
desc: 'Break through: planning widgets plus challenge widgets to pressure-test your program.',
|
| 239 |
icon: 'gavel',
|
| 240 |
layout: presetIds([
|
| 241 |
{ type: 'writing', size: 'M' },
|
|
|
|
| 291 |
documenter: { entries: [], lastSummary: null },
|
| 292 |
'phd-journey': {
|
| 293 |
// Status per milestone: 'open' | 'in-progress' | 'completed'
|
| 294 |
+
// Milestones come from the standard training roadmap (assessment → maintenance)
|
| 295 |
statuses: {},
|
| 296 |
notes: {},
|
| 297 |
},
|
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
// User Guide content for
|
| 2 |
// Use {{appName}} as a placeholder — replaced at render time.
|
| 3 |
|
| 4 |
export const userGuideTopics = [
|
|
@@ -8,12 +8,12 @@ export const userGuideTopics = [
|
|
| 8 |
icon: 'Sparkles',
|
| 9 |
content: `# Welcome to {{appName}}
|
| 10 |
|
| 11 |
-
{{appName}} is your AI-powered
|
| 12 |
|
| 13 |
## Your first steps
|
| 14 |
1. **Start a new chat** using the pencil icon next to the search bar
|
| 15 |
-
2. **Type a question** about
|
| 16 |
-
3. **Read multiple advisor responses** — each
|
| 17 |
4. **Reply to a specific advisor** to go deeper on their angle
|
| 18 |
|
| 19 |
## Need help?
|
|
@@ -21,11 +21,11 @@ Return to this guide anytime via the **?** icon in the header.`,
|
|
| 21 |
},
|
| 22 |
{
|
| 23 |
id: 'advisors',
|
| 24 |
-
title: 'Your
|
| 25 |
-
icon: '
|
| 26 |
-
content: `# Your
|
| 27 |
|
| 28 |
-
{{appName}} includes {{advisorCount}} specialized
|
| 29 |
|
| 30 |
## Available advisors
|
| 31 |
{{advisorList}}
|
|
@@ -40,15 +40,15 @@ Click the **advisors** dropdown in the top right of the chat to see the full pan
|
|
| 40 |
content: `# Conversations & Replies
|
| 41 |
|
| 42 |
## Asking a question
|
| 43 |
-
Type into the chat box at the bottom. All
|
| 44 |
|
| 45 |
-
## Replying to a specific
|
| 46 |
-
Click
|
| 47 |
|
| 48 |
## Tips
|
| 49 |
-
- Include
|
| 50 |
-
- Paste
|
| 51 |
-
- Different
|
| 52 |
},
|
| 53 |
{
|
| 54 |
id: 'documents',
|
|
@@ -56,18 +56,18 @@ Click an advisor's response to **reply directly to them** and continue one-on-on
|
|
| 56 |
icon: 'Paperclip',
|
| 57 |
content: `# Uploading Documents
|
| 58 |
|
| 59 |
-
Attach **PDFs, Word documents, and text files** so
|
| 60 |
|
| 61 |
## How it works
|
| 62 |
1. Click the paperclip icon in the chat input
|
| 63 |
2. Select your file
|
| 64 |
3. Wait for processing
|
| 65 |
-
4. Ask a question —
|
| 66 |
|
| 67 |
## Good uploads
|
| 68 |
-
-
|
| 69 |
-
-
|
| 70 |
-
-
|
| 71 |
},
|
| 72 |
{
|
| 73 |
id: 'sessions',
|
|
@@ -79,18 +79,18 @@ Every conversation is saved as a session. Use the sidebar search to find past ch
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
id: 'canvas',
|
| 82 |
-
title: '
|
| 83 |
icon: 'BarChart3',
|
| 84 |
content: `# {{appName}} Canvas
|
| 85 |
|
| 86 |
-
The Canvas is a **structured workspace** for your
|
| 87 |
|
| 88 |
-
-
|
| 89 |
-
-
|
| 90 |
-
-
|
| 91 |
-
-
|
| 92 |
-
-
|
| 93 |
-
-
|
| 94 |
|
| 95 |
Open Canvas from the sidebar. Layout and widgets auto-save in your browser.`,
|
| 96 |
},
|
|
@@ -101,10 +101,10 @@ Open Canvas from the sidebar. Layout and widgets auto-save in your browser.`,
|
|
| 101 |
content: `# Tips & Shortcuts
|
| 102 |
|
| 103 |
## Useful workflows
|
| 104 |
-
- **
|
| 105 |
-
- **
|
| 106 |
-
- **
|
| 107 |
-
- **
|
| 108 |
|
| 109 |
## Theme
|
| 110 |
Switch light/dark mode from the toggle in the header.`,
|
|
|
|
| 1 |
+
// User Guide content for MuscleGrowthAI.
|
| 2 |
// Use {{appName}} as a placeholder — replaced at render time.
|
| 3 |
|
| 4 |
export const userGuideTopics = [
|
|
|
|
| 8 |
icon: 'Sparkles',
|
| 9 |
content: `# Welcome to {{appName}}
|
| 10 |
|
| 11 |
+
{{appName}} is your AI-powered fitness and bodybuilding guidance system. A panel of specialized coaches gives you diverse perspectives on training, hypertrophy, nutrition, recovery, form, and progress tracking.
|
| 12 |
|
| 13 |
## Your first steps
|
| 14 |
1. **Start a new chat** using the pencil icon next to the search bar
|
| 15 |
+
2. **Type a question** about workouts, muscle groups, nutrition, recovery, or your goals
|
| 16 |
+
3. **Read multiple advisor responses** — each coach brings a different lens
|
| 17 |
4. **Reply to a specific advisor** to go deeper on their angle
|
| 18 |
|
| 19 |
## Need help?
|
|
|
|
| 21 |
},
|
| 22 |
{
|
| 23 |
id: 'advisors',
|
| 24 |
+
title: 'Your Coaches',
|
| 25 |
+
icon: 'Dumbbell',
|
| 26 |
+
content: `# Your Coaches
|
| 27 |
|
| 28 |
+
{{appName}} includes {{advisorCount}} specialized fitness personas, each focused on a different part of building muscle safely and effectively.
|
| 29 |
|
| 30 |
## Available advisors
|
| 31 |
{{advisorList}}
|
|
|
|
| 40 |
content: `# Conversations & Replies
|
| 41 |
|
| 42 |
## Asking a question
|
| 43 |
+
Type into the chat box at the bottom. All coaches respond with their unique perspective.
|
| 44 |
|
| 45 |
+
## Replying to a specific coach
|
| 46 |
+
Click a coach's response to **reply directly to them** and continue one-on-one.
|
| 47 |
|
| 48 |
## Tips
|
| 49 |
+
- Include context (goal, experience level, equipment, days per week, injuries)
|
| 50 |
+
- Paste your current program, macros, or a training log for sharper advice
|
| 51 |
+
- Different coaches may prioritize differently — use that to balance gains, recovery, and safety`,
|
| 52 |
},
|
| 53 |
{
|
| 54 |
id: 'documents',
|
|
|
|
| 56 |
icon: 'Paperclip',
|
| 57 |
content: `# Uploading Documents
|
| 58 |
|
| 59 |
+
Attach **PDFs, Word documents, and text files** so coaches can reference your materials.
|
| 60 |
|
| 61 |
## How it works
|
| 62 |
1. Click the paperclip icon in the chat input
|
| 63 |
2. Select your file
|
| 64 |
3. Wait for processing
|
| 65 |
+
4. Ask a question — coaches use **RAG** to pull relevant sections
|
| 66 |
|
| 67 |
## Good uploads
|
| 68 |
+
- Current training programs and splits
|
| 69 |
+
- Nutrition plans, macro breakdowns, and food logs
|
| 70 |
+
- Progress spreadsheets or bloodwork summaries (exported as PDF)`,
|
| 71 |
},
|
| 72 |
{
|
| 73 |
id: 'sessions',
|
|
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
id: 'canvas',
|
| 82 |
+
title: 'Fitness Canvas',
|
| 83 |
icon: 'BarChart3',
|
| 84 |
content: `# {{appName}} Canvas
|
| 85 |
|
| 86 |
+
The Canvas is a **structured workspace** for your training. Insights from chats can inform widgets such as:
|
| 87 |
|
| 88 |
+
- Training split & weekly plan
|
| 89 |
+
- Target muscle groups & volume
|
| 90 |
+
- Nutrition & protein goals
|
| 91 |
+
- Recovery, sleep & mobility
|
| 92 |
+
- Progress metrics & PRs
|
| 93 |
+
- Program adjustments over time
|
| 94 |
|
| 95 |
Open Canvas from the sidebar. Layout and widgets auto-save in your browser.`,
|
| 96 |
},
|
|
|
|
| 101 |
content: `# Tips & Shortcuts
|
| 102 |
|
| 103 |
## Useful workflows
|
| 104 |
+
- **Program design:** Hypertrophy Coach + Program Planner
|
| 105 |
+
- **Eating for your goal:** Nutrition Strategist + Hypertrophy Coach
|
| 106 |
+
- **Staying injury-free:** Form & Safety Coach + Recovery Specialist
|
| 107 |
+
- **Breaking a plateau:** Ask multiple coaches to compare volume, intensity, and recovery
|
| 108 |
|
| 109 |
## Theme
|
| 110 |
Switch light/dark mode from the toggle in the header.`,
|
|
@@ -18,7 +18,7 @@ import ProfileWalkthrough from '../components/ProfileWalkthrough';
|
|
| 18 |
import ClearDataModal from '../components/ClearDataModal';
|
| 19 |
import AccountModal from '../components/AccountModal';
|
| 20 |
|
| 21 |
-
const ACTIVE_ADVISORS_STORAGE_KEY = '
|
| 22 |
|
| 23 |
const ChatPage = ({ user, authToken, onNavigateToHome, onNavigateToCanvas, onSignOut }) => {
|
| 24 |
const { config, advisors, getAdvisorColors } = useAppConfig();
|
|
@@ -1081,6 +1081,32 @@ const handleNewChat = async (sessionId = null) => {
|
|
| 1081 |
</div>
|
| 1082 |
</div>
|
| 1083 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
</div>
|
| 1085 |
);
|
| 1086 |
};
|
|
|
|
| 18 |
import ClearDataModal from '../components/ClearDataModal';
|
| 19 |
import AccountModal from '../components/AccountModal';
|
| 20 |
|
| 21 |
+
const ACTIVE_ADVISORS_STORAGE_KEY = 'muscleGrowthActiveAdvisorIds';
|
| 22 |
|
| 23 |
const ChatPage = ({ user, authToken, onNavigateToHome, onNavigateToCanvas, onSignOut }) => {
|
| 24 |
const { config, advisors, getAdvisorColors } = useAppConfig();
|
|
|
|
| 1081 |
</div>
|
| 1082 |
</div>
|
| 1083 |
</div>
|
| 1084 |
+
|
| 1085 |
+
{showProfileForm && (
|
| 1086 |
+
<ProfileWalkthrough
|
| 1087 |
+
authToken={authToken}
|
| 1088 |
+
existingProfile={userProfile}
|
| 1089 |
+
onClose={() => { setShowProfileForm(false); loadProfile(); }}
|
| 1090 |
+
/>
|
| 1091 |
+
)}
|
| 1092 |
+
|
| 1093 |
+
{showAccount && (
|
| 1094 |
+
<AccountModal
|
| 1095 |
+
user={user}
|
| 1096 |
+
authToken={authToken}
|
| 1097 |
+
onClose={() => setShowAccount(false)}
|
| 1098 |
+
onAccountUpdated={() => setShowAccount(false)}
|
| 1099 |
+
onAccountDeleted={() => { setShowAccount(false); onSignOut?.(); }}
|
| 1100 |
+
/>
|
| 1101 |
+
)}
|
| 1102 |
+
|
| 1103 |
+
{showClearData && (
|
| 1104 |
+
<ClearDataModal
|
| 1105 |
+
authToken={authToken}
|
| 1106 |
+
onClose={() => setShowClearData(false)}
|
| 1107 |
+
onDataCleared={() => loadProfile()}
|
| 1108 |
+
/>
|
| 1109 |
+
)}
|
| 1110 |
</div>
|
| 1111 |
);
|
| 1112 |
};
|