Skip to content

Commit 14eb52a

Browse files
committed
Improve mobile sidebar and diff section serialization
1 parent 6002c1f commit 14eb52a

4 files changed

Lines changed: 117 additions & 22 deletions

File tree

frontend/css/mobile.css

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
/* Responsive overrides — small viewports up to 700px, narrow phones up to 400px. */
22
@media(max-width:700px){
3-
/* Sidebar becomes a slide-in drawer */
3+
/* Sidebar becomes a slide-in drawer. Use a comfortable fixed width rather than
4+
--sw: on a phone-width viewport the desktop resize logic clamps --sw down to
5+
its 200px minimum, which collapses the session-name column to a few pixels. */
46
#sb{
57
position:fixed;top:0;left:0;
68
height:100vh;height:100dvh;
79
z-index:100;
8-
width:var(--sw)!important;min-width:var(--sw)!important;
10+
width:min(86vw,330px)!important;min-width:0!important;
911
transform:translateX(-100%);
1012
transition:transform .22s ease;
1113
overflow:hidden;
1214
}
1315
#sb.mobile-open{transform:translateX(0)!important}
14-
#sb.collapsed{transform:translateX(-100%)!important;width:var(--sw)!important;min-width:var(--sw)!important}
16+
#sb.collapsed{transform:translateX(-100%)!important;width:min(86vw,330px)!important;min-width:0!important}
1517
.sb-expand{display:none!important}
1618
/* No edge-resizing in the mobile drawer. */
1719
.sb-resizer{display:none!important}
1820
.mob-menu-btn{display:flex}
1921

22+
/* Touch has no hover, so the per-row actions (favorite / tracker / rename /
23+
delete) would otherwise stay invisible yet still reserve width, squeezing
24+
the session title. Reveal them and let the title truncate cleanly. */
25+
.si-act{opacity:1}
26+
.si-meta{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
27+
2028
/* Main fills the full screen — dvh keeps it within the actual visible area on iOS */
2129
#main{
2230
width:100vw;min-width:0;

frontend/css/sidebar.css

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
.sb-resizer:hover::after,body.sb-resizing .sb-resizer::after{background:var(--ab)}
1111
#sb.collapsed .sb-resizer{display:none}
1212
body.sb-resizing{cursor:col-resize;user-select:none}
13-
.sb-expand{position:absolute;left:8px;top:62px;z-index:30;width:34px;height:34px;background:var(--bg3);border:1px solid var(--border2);border-radius:9px;color:var(--text2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s}
14-
.sb-expand:hover{background:var(--bg4);color:var(--text)}
13+
/* Lives in the toolbar; hidden until the sidebar is collapsed (toggleSidebar
14+
flips display to flex). Sits where the mobile menu button does. */
15+
.sb-expand{display:none;width:34px;height:34px;background:none;border:1px solid var(--border);border-radius:8px;color:var(--text2);cursor:pointer;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s}
16+
.sb-expand:hover{background:var(--bg3);color:var(--text)}
1517
.sb-collapse{margin-left:auto;background:none;border:none;color:var(--text3);cursor:pointer;padding:4px 6px;border-radius:6px;display:flex;align-items:center;justify-content:center}
1618
.sb-collapse:hover{color:var(--text);background:var(--bg3)}
1719
.sb-head{padding:18px 14px 12px;border-bottom:1px solid var(--border)}

frontend/index.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,6 @@
121121
</button>
122122
</div>
123123
</div>
124-
<button id="sb-expand" class="sb-expand" onclick="toggleSidebar()" title="Expand sidebar" style="display:none">
125-
<svg class="ic sm"><use href="#ic-list"/></svg>
126-
</button>
127124
<div class="ss">
128125
<div class="sl" id="sl-fav" style="display:none">Favorites</div>
129126
<div id="sessions-list-fav"></div>
@@ -152,6 +149,11 @@
152149
<button class="mob-menu-btn" onclick="openMobileSidebar()" title="Open menu">
153150
<svg class="ic"><use href="#ic-list"/></svg>
154151
</button>
152+
<!-- Desktop: expand a collapsed sidebar. Lives in the toolbar (not inside #sb,
153+
which clips to width:0 when collapsed) so it stays clickable. -->
154+
<button id="sb-expand" class="sb-expand" onclick="toggleSidebar()" title="Expand sidebar" style="display:none">
155+
<svg class="ic"><use href="#ic-list"/></svg>
156+
</button>
155157
<div class="tb-title" id="tb-title">StackResume</div>
156158
<div class="tb-status" id="tb-status"><div class="phdr-dot"></div><span id="tb-status-txt">Generating…</span></div>
157159
<div class="prov-pill" onclick="openSettings()" title="Click to change model"><div class="pdot"></div><span id="prov-lbl">openai / gpt-4o</span></div>

frontend/js/diff.js

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,29 +113,112 @@ function _diffWords(a,b){
113113
return parts.map(p=>p.t==='='?esc(p.v):p.t==='+'?`<span class="df-add">${esc(p.v)}</span>`:`<span class="df-del">${esc(p.v)}</span>`).join('');
114114
}
115115

116+
// ── Section serializers ───────────────────────────────────────────────────────
117+
// Turn each resume section into diff-friendly plain text: one logical entry per
118+
// block, with a blank line between blocks so the line-diff aligns whole entries.
119+
// A section/entry present only in the new version reads as all-added (green);
120+
// one dropped from the new version reads as all-removed (red). Field ordering
121+
// mirrors the preview so the diff reads naturally. ss()/toArr() come from
122+
// preview.js and coerce the LLM's loose field shapes.
123+
const _j=(arr,sep)=>arr.filter(x=>x!=null&&x!=='').join(sep);
124+
125+
function _seEdu(list){return toArr(list).map(e=>{
126+
const deg=ss(e.degree)+(ss(e.field_of_study)?` in ${ss(e.field_of_study)}`:'');
127+
const L=[_j([deg,ss(e.institution),ss(e.location),_j([ss(e.start_date),ss(e.end_date)],' – ')],' · ')];
128+
if(ss(e.gpa))L.push('GPA: '+ss(e.gpa));
129+
if(ss(e.honors))L.push(ss(e.honors));
130+
const cw=toArr(e.relevant_coursework).map(ss).filter(Boolean);if(cw.length)L.push('Coursework: '+cw.join(', '));
131+
const ac=toArr(e.activities).map(ss).filter(Boolean);if(ac.length)L.push('Activities: '+ac.join(', '));
132+
return L.join('\n');
133+
}).join('\n\n');}
134+
135+
function _seProjects(list){return toArr(list).map(p=>{
136+
const L=[_j([ss(p.name),ss(p.role),ss(p.type),_j([ss(p.start_date),ss(p.end_date)],' – ')],' · ')];
137+
if(ss(p.description))L.push(ss(p.description));
138+
const tech=toArr(p.technologies).map(ss).filter(Boolean);if(tech.length)L.push('Tech: '+tech.join(', '));
139+
toArr(p.highlights).map(ss).filter(Boolean).forEach(h=>L.push('• '+h));
140+
const url=ss(p.url)||ss(p.github);if(url)L.push(url);
141+
return L.join('\n');
142+
}).join('\n\n');}
143+
144+
function _seOSS(list){return toArr(list).map(o=>{
145+
const L=[_j([ss(o.project),ss(o.role),ss(o.language),ss(o.stars)?'★ '+ss(o.stars):''],' · ')];
146+
if(ss(o.description))L.push(ss(o.description));
147+
toArr(o.contributions).map(ss).filter(Boolean).forEach(c=>L.push('• '+c));
148+
if(ss(o.url))L.push(ss(o.url));
149+
return L.join('\n');
150+
}).join('\n\n');}
151+
152+
function _seCerts(list){return toArr(list).map(c=>{
153+
const d=[];if(ss(c.date))d.push('issued '+ss(c.date));if(ss(c.expiry))d.push('expires '+ss(c.expiry));
154+
return _j([ss(c.name),ss(c.issuer),d.join(', '),ss(c.credential_id)?'ID '+ss(c.credential_id):'',ss(c.url)],' · ');
155+
}).join('\n');}
156+
157+
function _sePubs(list){return toArr(list).map(p=>
158+
_j([ss(p.title),ss(p.type),ss(p.venue),ss(p.date),toArr(p.authors).map(ss).filter(Boolean).join(', '),ss(p.url)],' · ')
159+
).join('\n');}
160+
161+
function _sePatents(list){return toArr(list).map(p=>{
162+
const L=[_j([ss(p.title),ss(p.patent_number),ss(p.date)],' · ')];
163+
if(ss(p.description))L.push(ss(p.description));
164+
return L.join('\n');
165+
}).join('\n\n');}
166+
167+
function _seVol(list){return toArr(list).map(v=>{
168+
const L=[_j([ss(v.role),ss(v.organization),_j([ss(v.start_date),ss(v.end_date)],' – ')],' · ')];
169+
if(ss(v.description))L.push(ss(v.description));
170+
return L.join('\n');
171+
}).join('\n\n');}
172+
173+
function _seLangs(list){return toArr(list).map(l=>
174+
typeof l==='string'?l:_j([ss(l.language),ss(l.proficiency)?`(${ss(l.proficiency)})`:''],' ')
175+
).join('\n');}
176+
177+
function _seContact(r){const pi=r.personal_info||{};
178+
return _j([ss(pi.full_name),ss(pi.professional_title),ss(pi.email),ss(pi.phone),ss(pi.location),ss(pi.linkedin),ss(pi.github),ss(pi.website)||ss(pi.portfolio)],'\n');
179+
}
180+
116181
function _buildDiffHtml(a,b){
182+
a=a||{};b=b||{};
117183
const secs=[];
118-
const check=(title,va,vb)=>{if(va!==vb)secs.push({title,html:_diffText(va||'',vb||'')});};
184+
const check=(title,va,vb)=>{if((va||'')!==(vb||''))secs.push({title,html:_diffText(va||'',vb||'')});};
119185

186+
check('Contact',_seContact(a),_seContact(b));
120187
check('Professional Summary',a.professional_summary,b.professional_summary);
121188

122-
const ccA=(a.core_competencies||[]).join(', ');
123-
const ccB=(b.core_competencies||[]).join(', ');
189+
const ccA=toArr(a.core_competencies).map(ss).filter(Boolean).join(', ');
190+
const ccB=toArr(b.core_competencies).map(ss).filter(Boolean).join(', ');
124191
check('Core Competencies',ccA,ccB);
125192

126-
const expB=b.experience||[];
127-
const expA=a.experience||[];
128-
expB.forEach((job,idx)=>{
129-
const pj=expA[idx];
130-
const txtA=pj?[...(pj.responsibilities||[]),...(pj.achievements||[])].join('\n'):'';
131-
const txtB=[...(job.responsibilities||[]),...(job.achievements||[])].join('\n');
132-
if(txtA!==txtB)secs.push({title:`Experience — ${job.title} @ ${job.company}`,html:_diffText(txtA,txtB)});
133-
});
134-
135-
const skA=Object.values(a.technical_skills||{}).flat().join(', ');
136-
const skB=Object.values(b.technical_skills||{}).flat().join(', ');
193+
// Experience — per job, so a small wording tweak in one bullet doesn't light up
194+
// the whole section. Walking the longer of the two lists also surfaces jobs
195+
// added to (or removed from) the new version, not just edited ones.
196+
const expA=a.experience||[],expB=b.experience||[];
197+
for(let i=0;i<Math.max(expA.length,expB.length);i++){
198+
const pj=expA[i],nj=expB[i],job=nj||pj;
199+
const txtA=pj?[...toArr(pj.responsibilities),...toArr(pj.achievements)].map(ss).join('\n'):'';
200+
const txtB=nj?[...toArr(nj.responsibilities),...toArr(nj.achievements)].map(ss).join('\n'):'';
201+
if(txtA!==txtB)secs.push({title:`Experience — ${ss(job.title)} @ ${ss(job.company)}`,html:_diffText(txtA,txtB)});
202+
}
203+
204+
const skA=Object.values(a.technical_skills||{}).flat().map(ss).join(', ');
205+
const skB=Object.values(b.technical_skills||{}).flat().map(ss).join(', ');
137206
check('Technical Skills',skA,skB);
138207

208+
// Remaining sections — these were previously never compared, so newly added
209+
// entries/sections (a fresh Projects list, a new certification, etc.) silently
210+
// failed to appear in the diff.
211+
check('Projects',_seProjects(a.projects),_seProjects(b.projects));
212+
check('Open Source',_seOSS(a.open_source_contributions),_seOSS(b.open_source_contributions));
213+
check('Education',_seEdu(a.education),_seEdu(b.education));
214+
check('Certifications',_seCerts(a.certifications),_seCerts(b.certifications));
215+
check('Publications',_sePubs(a.publications),_sePubs(b.publications));
216+
check('Patents',_sePatents(a.patents),_sePatents(b.patents));
217+
check('Awards & Honors',toArr(a.awards_and_honors).map(ss).filter(Boolean).join('\n'),toArr(b.awards_and_honors).map(ss).filter(Boolean).join('\n'));
218+
check('Volunteer Experience',_seVol(a.volunteer_experience),_seVol(b.volunteer_experience));
219+
check('Languages',_seLangs(a.languages),_seLangs(b.languages));
220+
check('Interests',toArr(a.interests).map(ss).filter(Boolean).join(', '),toArr(b.interests).map(ss).filter(Boolean).join(', '));
221+
139222
const metaA=a.metadata||{},metaB=b.metadata||{};
140223
const scoreKeys=['overall_score','ats_score','quality_score','impact_score'];
141224
if(scoreKeys.some(k=>metaA[k]!==metaB[k])){

0 commit comments

Comments
 (0)