Skip to content

Feature: Inline rendering of Solid resources using SolidOS panes #20

Description

@melvincarvalho

Concept

Enable rich inline previews of Solid resources when pasted in chat. Instead of just showing a link, detect the RDF type and render it using the appropriate SolidOS pane.

Example Use Cases

Sharing a contact:

Alice: Check out this person @https://bob.pod/profile/card#me

→ Renders inline contact card with name, photo, email

Sharing a meeting:

Bob: Join standup @https://team.pod/meetings/daily.ttl#this

→ Renders meeting details, time, participants, join button

Sharing a task:

Charlie: Can you review @https://project.pod/tasks/feature-x#task1

→ Renders task card with status, assignee, deadline

Sharing a document:

Dave: See the spec @https://docs.pod/specs/api-v2.ttl#this

→ Renders document preview with title, author, last modified

How It Works with mashlib

mashlib Pane System

SolidOS/mashlib has a pane registry where panes register what RDF types they can handle:

// Example: contact pane registration{icon: 'https://solid.github.io/solid-ui/src/icons/noun_99101.svg',name: 'contact',label: (subject,context)=>'Contact',// What types this pane handlesmintClass: ns.vcard('Individual'),// Can it render this resource?shouldRender: (subject,context)=>{consttypes=context.session.store.findTypeURIs(subject)returntypes['http://www.w3.org/2006/vcard/ns#Individual']},// Render functionrender: (subject,context)=>{constdiv=context.dom.createElement('div')// ... render contact cardreturndiv}}

Dynamic Pane Registration

Panes are registered in mashlib's panes/index.js:

import{contactPane}from'./contact/contactPane'import{schedulePane}from'./schedule/schedulePane'import{issuePane}from'./issue/issuePane'exportconstpanes={list: [contactPane,schedulePane,issuePane, ...],byName: (name)=>panes.list.find(p=>p.name===name),byType: (type)=>panes.list.find(p=>p.mintClass?.uri===type)}

Implementation Approach

Option 1: Import mashlib (Full Integration)

import{panes}from'mashlib'asyncfunctionrenderSolidResource(uri,dom,context){// 1. Fetch resourceconstsubject=$rdf.sym(uri)awaitstore.fetcher.load(subject.doc())// 2. Get RDF typesconsttypes=store.each(subject,RDF('type'))// 3. Find matching panefor(consttypeoftypes){constpane=panes.byType(type.value)if(pane&&pane.shouldRender(subject,context)){// 4. Render with panereturnpane.render(subject,context)}}// 5. Fallback: generic RDF viewerreturnrenderGenericRDF(subject,dom,context)}

Pros:

  • Full SolidOS ecosystem integration
  • Access to all existing panes
  • Automatic updates when panes are added

Cons:

  • Heavy dependency (mashlib is large)
  • May pull in unnecessary UI code

Option 2: Minimal Pane Registry (Lightweight)

// Define minimal pane interfaceconstminiPanes={contact: {type: 'http://www.w3.org/2006/vcard/ns#Individual',render: async(subject,store,dom)=>{constname=store.any(subject,VCARD('fn'))constphoto=store.any(subject,VCARD('hasPhoto'))constcard=dom.createElement('div')card.className='contact-preview'if(photo)card.innerHTML=`<img src="${photo.value}" />`card.innerHTML+=`<h3>${name?.value||'Unknown'}</h3>`returncard}},meeting: {type: 'http://www.w3.org/ns/pim/meeting#Meeting',render: async(subject,store,dom)=>{// ... render meeting}}}// Use itasyncfunctionrenderResource(uri){constsubject=$rdf.sym(uri)awaitstore.fetcher.load(subject.doc())consttypes=store.each(subject,RDF('type'))for(consttypeoftypes){constpane=Object.values(miniPanes).find(p=>p.type===type.value)if(pane)returnpane.render(subject,store,dom)}returnnull// No pane found}

Pros:

  • Lightweight
  • Full control
  • Easy to add custom renderers

Cons:

  • Manual maintenance
  • Limited to types we implement
  • Doesn't leverage SolidOS ecosystem

Option 3: Hybrid (Recommended)

// 1. Try mashlib panes first (if available)letrendered=nullif(window.mashlib){constpane=mashlib.panes.byType(type)if(pane)rendered=pane.render(subject,context)}// 2. Fallback to mini registryif(!rendered){constminiPane=miniPanes[type]if(miniPane)rendered=miniPane.render(subject,store,dom)}// 3. Final fallback: generic link previewif(!rendered){rendered=renderGenericPreview(subject,store,dom)}

Pros:

  • Best of both worlds
  • Works standalone or with mashlib
  • Progressive enhancement

Message Content Parsing

Detect Solid URIs

functionrenderMessageContent(dom,content){constcontainer=dom.createElement('div')consttokens=[]letlastIndex=0// Match Solid URIs (ending in .ttl or #fragment)constSOLID_URI=/(https?:\/\/[^\s]+(?:\.ttl|#\w+))/gcontent.replace(SOLID_URI,(match,uri,index)=>{if(index>lastIndex){tokens.push({type: 'text',value: content.slice(lastIndex,index)})}tokens.push({type: 'solid-resource', uri })lastIndex=index+match.length})// Render tokensfor(consttokenoftokens){if(token.type==='solid-resource'){constpreview=awaitrenderSolidResource(token.uri,dom,context)container.appendChild(preview)}else{container.appendChild(dom.createTextNode(token.value))}}returncontainer}

Async Loading

// Show placeholder while loadingconstplaceholder=dom.createElement('div')placeholder.className='resource-loading'placeholder.textContent=`Loading ${uri}...`container.appendChild(placeholder)// Load and renderrenderSolidResource(uri,dom,context).then(preview=>{placeholder.replaceWith(preview)}).catch(err=>{placeholder.textContent=`Error loading resource: ${err.message}`})

UI Design

Inline Preview Card

.resource-preview {
border:1px solid #e1e4e8;
border-radius:6px;
padding:16px;
margin:12px0;
background:linear-gradient(135deg,#667eea0%,#764ba2100%);
background: white;
box-shadow:01px3pxrgba(0,0,0,0.12);
max-width:400px;
transition: box-shadow 0.2s;
}
.resource-preview:hover {
box-shadow:04px12pxrgba(0,0,0,0.15);
}
.resource-preview-header {
display: flex;
align-items: center;
gap:8px;
margin-bottom:12px;
padding-bottom:12px;
border-bottom:1px solid #e1e4e8;
}
.resource-preview-icon {
width:20px;
height:20px;
opacity:0.8;
}
.resource-preview-type {
font-size:11px;
text-transform: uppercase;
letter-spacing:0.5px;
color:#6a737d;
font-weight:600;
}
.resource-preview-content {
/* Pane-specific content */
}
.resource-preview-link {
display: inline-block;
margin-top:12px;
font-size:13px;
color:#0366d6;
text-decoration: none;
font-weight:500;
}
.resource-preview-link:hover {
text-decoration: underline;
}

Example Rendered Cards

Contact Card

👤 CONTACT


🧑‍💼 Bob Smith
📧 bob@example.com
📱 +1-555-0123

View full profile →

Meeting Card

📅 MEETING


Daily Standup
🕐 Today at 10:00 AM
👥 3 participants

Join Meeting · View details →

Document Card

📄 DOCUMENT


API Specification v2.0
✍️ Alice Johnson
🕒 Updated 2 hours ago

Read document →

Common Types to Support

RDF TypePaneExample
vcard:Individualcontact-panePerson profiles
meeting:Meetingschedule-paneMeetings, events
schema:ImageObjectimage-panePhotos (already works)
foaf:Documentdocument-paneDocuments, notes
ui:Formform-paneInteractive forms
issue:Issueissue-paneBug reports, tasks
cal:Eventschedule-paneCalendar events

Benefits

1. Semantic Rich Previews

  • Not just links, but meaningful data
  • Context-aware rendering
  • Interactive widgets

2. SolidOS Ecosystem Integration

  • Leverage existing panes
  • Consistent UI across apps
  • Community-maintained renderers

3. Extensibility

  • Easy to add new types
  • Custom panes for app-specific types
  • Plugin architecture

4. Better UX

  • Less context switching
  • Preview before clicking
  • Inline actions (join meeting, add contact, etc.)

Implementation Tasks

  • Add Solid URI detection to message parser
  • Implement async resource loading
  • Create minimal pane registry for common types
  • Design preview card UI
  • Add mashlib integration (optional)
  • Handle loading states and errors
  • Add expand/collapse for large previews
  • Support inline actions (view, edit, react)
  • Add configuration (enable/disable previews)
  • Document how to add custom panes

References

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions