Skip to content

Conversation

@goblincore
Copy link
Collaborator

@goblincore goblincore commented Jan 27, 2026

Overview

🎟 Relevant Jira Issues

📚 What is the context and goal of this PR?

🥴 TL; RL:

💡 Feature Breakdown (screenshots & videos encouraged!)

This adds some very simple conditional rendering logic:

  if (req.accepts('html')) {
            const framework = await getSkillFrameworkById(frameworkId);
            return res.send(
                renderSkillPage({
                    skill: skill as any,
                    framework: { id: frameworkId, name: framework?.name || 'Skill Framework' },
                })
            );
        }

        return res.json({
            id: skill.id,
            code: skill.code,
            statement: skill.statement,
            description: skill.description,
            type: skill.type,
            status: skill.status,
            icon: (skill as any).icon,
        });
        
   // New compound route for framework-scoped skill
    fastify.get('/frameworks/:frameworkId/skills/:skillId', async (request, reply) => {
        try {
            const { frameworkId, skillId } = request.params as { frameworkId: string; skillId: string };
            const skill = await getSkillByFrameworkAndId(frameworkId, skillId);

            if (!skill) {
                return reply.status(404).send({ error: 'Skill not found' });
            }

            const acceptHeader = request.headers.accept || '';
            const acceptsHtml = acceptHeader.split(',').some(type => type.trim().startsWith('text/html'));
            if (acceptsHtml) {
                const framework = await getSkillFrameworkById(frameworkId);
                return reply.type('text/html').send(
                    renderSkillPage({
                        skill: skill as any,
                        framework: { id: frameworkId, name: framework?.name || 'Skill Framework' },
                    })
                );
            }

            return reply.send({
                id: skill.id,
                code: skill.code,
                statement: skill.statement,
                description: skill.description,
                type: skill.type,
                status: skill.status,
                icon: (skill as any).icon,
            });
        } catch (error) {
            console.error('Error fetching skill by framework/id:', error);
            return reply.status(500).send({ error: 'Internal server error' });
        }
    });      
        

I suppose one could make more complex rendering or use some kind of minimal templating language or static site type framework but for now i am keeping it way simple by just using straight html no external dependencies or anything else needed, which for something simple like this i think is fine.

So in a browser environment it looks like this (if request header is "Accept: text/html")
Screenshot 2026-01-27 at 5 17 21 PM

Screenshot 2026-01-27 at 5 17 30 PM

If request header is "Accept: application/json " then it will return json

This also adds a learncard version by adding an new env variable.

Unrelated but when logging out scouts doesnt show a logging out modal so i added that here too.

🛠 Important tradeoffs made:

🔍 Types of Changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Chore (refactor, documentation update, etc)

💳 Does This Create Any New Technical Debt? ( If yes, please describe and add JIRA TODOs )

  • No
  • Yes

Testing

🔬 How Can Someone QA This?

Have to run dockerized scouts and make a skills framework and make a boost with the skill and click the link (remove https in the link to see locally)

📱 🖥 Which devices would you like help testing on?

🧪 Code Coverage

Documentation

📝 Documentation Checklist

User-Facing Docs (docs/docs.learncard.com)

  • Tutorial — New capability that users need to learn (docs/tutorials/)
  • How-To Guide — New workflow or integration (docs/how-to-guides/)
  • Reference — New/changed API, config, or SDK method (docs/sdks/)
  • Concept — New mental model or architecture explanation (docs/core-concepts/)
  • App Flows — Changes to LearnCard App or ScoutPass user flows (docs/apps/)

Internal/AI Docs

  • CLAUDE.md — New pattern, flow, or context that AI assistants need
  • Code comments/JSDoc — Complex logic that needs inline explanation

Visual Documentation

  • Mermaid diagram — Complex flow, state machine, or architecture

💭 Documentation Notes

✅ PR Checklist

  • Related to a Jira issue (create one if not)
  • My code follows style guidelines (eslint / prettier)
  • I have manually tested common end-2-end cases
  • I have reviewed my code
  • I have commented my code, particularly where ambiguous
  • New and existing unit tests pass locally with my changes
  • I have completed the Documentation Checklist above (or explained why N/A)

🚀 Ready to squash-and-merge?:

  • Code is backwards compatible
  • There is not a "Do Not Merge" label on this PR
  • I have thoughtfully considered the security implications of this change.
  • This change does not expose new public facing endpoints that do not have authentication

✨ PR Description

Purpose: Add HTML content negotiation to skills-viewer API route to render branded skill pages for web crawlers and social media sharing.

Main changes:

  • Created renderSkillPage helper with brand-specific theming (Scouts vs LCA) and OpenGraph meta tags for social sharing
  • Implemented Accept header-based content negotiation returning HTML for browsers or JSON for API clients
  • Added LoggingOutModal component replacing IonLoading to improve logout UX with custom modal implementation

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Description using Guidelines Learn how

@changeset-bot
Copy link

changeset-bot bot commented Jan 27, 2026

⚠️ No Changeset found

Latest commit: 1079e75

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify
Copy link

netlify bot commented Jan 27, 2026

Deploy Preview for learncarddocs canceled.

Name Link
🔨 Latest commit 1079e75
🔍 Latest deploy log https://app.netlify.com/projects/learncarddocs/deploys/697a54b639341900083049ae

@github-actions
Copy link
Contributor

👋 Hey there! It looks like you modified code, but didn't update the documentation in /docs.

If this PR introduces new features, changes APIs, or modifies behavior that users or developers need to know about, please consider updating the docs.


🏄 Windsurf Tip

You can ask Windsurf to help:

"Analyze the changes in this PR and update the gitbook docs in /docs accordingly."

Windsurf will review your changes and suggest appropriate documentation updates based on what was modified.


📚 Documentation Guide
Change Type Doc Location
New feature/API docs/tutorials/ or docs/how-to-guides/
SDK/API changes docs/sdks/
New concepts docs/core-concepts/
App UI/UX flows docs/apps/ (LearnCard App, ScoutPass)
Internal patterns CLAUDE.md

This is an automated reminder. If no docs are needed, feel free to ignore this message.

@netlify
Copy link

netlify bot commented Jan 27, 2026

Deploy Preview for staging-learncardapp failed. Why did it fail? →

Name Link
🔨 Latest commit 1079e75
🔍 Latest deploy log https://app.netlify.com/projects/staging-learncardapp/deploys/697a54b643b1770008188d03

Copy link
Contributor

@gitstream-cm gitstream-cm bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ PR Review

The PR adds content negotiation and HTML rendering for skills-viewer routes, along with a logout modal UI. The implementation is generally sound, but there's a critical XSS vulnerability in the HTML rendering function that must be addressed before merging.

2 issues detected:

🔒 Security - Unescaped user input in HTML context enables XSS attacks

Details: User-controlled data from skill and framework objects is directly interpolated into HTML without escaping. An attacker could inject malicious scripts through fields like skill.statement, skill.description, skill.icon, or framework.name that would execute in users' browsers.
File: services/learn-card-network/brain-service/src/helpers/skill-renderer.ts

🐞 Bug - Naive string matching for Accept header could incorrectly match partial MIME types 🛠️

Details: The Fastify implementation uses string inclusion check for content negotiation which could match unwanted MIME types (e.g., 'text/html-backup'), while the Express version uses the proper req.accepts() method. This inconsistency could lead to different behavior between the two implementations.
File: services/learn-card-network/brain-service/src/skills-viewer.ts (130-130)
🛠️ A suggested code correction is included in the review comments.

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

@gitstream-cm
Copy link
Contributor

gitstream-cm bot commented Jan 27, 2026

🥷 Code experts: TaylorBeeston, Custard7

TaylorBeeston, Custard7 have most 👩‍💻 activity in the files.
TaylorBeeston, Custard7 have most 🧠 knowledge in the files.

See details

apps/scouts/src/hooks/useLogout.tsx

Activity based on git-commit:

TaylorBeeston Custard7
JAN
DEC 6 additions & 7 deletions
NOV 82 additions & 0 deletions
OCT
SEP
AUG

Knowledge based on git-blame:
TaylorBeeston: 100%

services/learn-card-network/brain-service/serverless.local.yml

Activity based on git-commit:

TaylorBeeston Custard7
JAN
DEC
NOV 11 additions & 0 deletions
OCT
SEP
AUG

Knowledge based on git-blame:
Custard7: 95%
TaylorBeeston: 5%

services/learn-card-network/brain-service/serverless.yml

Activity based on git-commit:

TaylorBeeston Custard7
JAN 4 additions & 1 deletions
DEC 5 additions & 0 deletions
NOV 11 additions & 0 deletions
OCT 1 additions & 0 deletions
SEP
AUG

Knowledge based on git-blame:
Custard7: 95%
TaylorBeeston: 5%

services/learn-card-network/brain-service/src/skills-viewer.ts

Activity based on git-commit:

TaylorBeeston Custard7
JAN
DEC
NOV 156 additions & 0 deletions
OCT
SEP
AUG

Knowledge based on git-blame:
TaylorBeeston: 100%

✨ Comment /gs review for LinearB AI review. Learn how to automate it here.

Co-authored-by: gitstream-cm[bot] <111687743+gitstream-cm[bot]@users.noreply.github.com>
Copy link
Contributor

@gitstream-cm gitstream-cm bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ PR Review

The PR successfully implements content negotiation for skill viewer endpoints with HTML rendering. The logout flow enhancement with visual feedback is well-structured. However, there is a brand configuration issue in the HTML renderer that affects social media preview URLs.

1 issues detected:

🐞 Bug - Social media preview URLs don't respect the brand configuration, always showing scoutnetwork.org domain even for LCA deployments. 🛠️

Details: The social media preview URL is hardcoded to scoutnetwork.org for all brands. When APP_BRAND is set to 'lca', OpenGraph and Twitter meta tags still reference scoutnetwork.org, causing social media shares to display incorrect URLs and potentially confuse users about which platform they're viewing.
File: services/learn-card-network/brain-service/src/helpers/skill-renderer.ts (21-24)
🛠️ A suggested code correction is included in the review comments.

Generated by LinearB AI and added by gitStream.
AI-generated content may contain inaccuracies. Please verify before using.
💡 Tip: You can customize your AI Review using Guidelines Learn how

Co-authored-by: gitstream-cm[bot] <111687743+gitstream-cm[bot]@users.noreply.github.com>
@goblincore goblincore merged commit c6e1c43 into main Jan 28, 2026
10 of 15 checks passed
@goblincore goblincore deleted the LC-1513-fix branch January 28, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants