(???) / ES2018 Hacker News client under 100 lines of code
 (???) onst baseUrl = 'https://hacker-news.firebaseio.com/v0/'
 (???) onst fetchAll = async (...endpoints) => {
 (???)    // TODO use forEach(async)
 (???)    const responses = await Promise.all(endpoints.map(endpoint => fetch(`${ baseUrl }${ endpoint }.json`)))
 (???)    responses.forEach(response => { if (!response.ok) throw Error(`${ response.status } ${ response.statusText }`) })
 (???)    return await Promise.all(responses.map(response => response.json()))
 (???) 
 (???) onst formatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'short', timeStyle: 'short' })
 (???) onst list = async ids => {
 (???)    document.querySelector('.hn').innerHTML = `<section><h2>Hacker News</h2><ol></ol></section>`
 (???)    const $list = document.querySelector('.hn ol')
 (???)    const items = await fetchAll(...ids.map(id => `item/${ id }`))                  // FIXME indeterminate order?
 (???)    for (const item of items) {                                 // TODO generator, infinite scrolling
 (???)        const $item = document.createElement('li')
 (???)        $list.appendChild($item)
 (???)        const domain = item.url ? new URL(item.url).host : ''   // ask, jobs and others
 (???)        $item.innerHTML = `<a href="${ item.url || '?id=' + item.id}">${ item.title }</a> <small>${ domain }</small><br>
 (???) small>${ item.score } points by <a href="?user=${ item.by }">${ item.by }</a> <a href="?id=${ item.id }">${ formatter.format(new Date(item.time * 1000)) }</a>
 (???) <a href="?id=${ item.id }">${ item.descendants || 0 } comments</a></small>`
 (???)    }
 (???) 
 (???) onst renderComments = async ($article, kids) => {
 (???)    if (!kids) return
 (???)    const items = await fetchAll(...kids.map(kid => `item/${ kid }`))
 (???)    for (const item of items) {
 (???)        const $comment = document.createElement('blockquote')
 (???)        $article.appendChild($comment)
 (???)        $comment.innerHTML = `<details open><summary>
 (???) small><a href="?user=${ item.by }">${ item.by || 'deleted'}</a> <a href="?id=${ item.id }">${ formatter.format(new Date(item.time * 1000)) }</a> ${item.dead ? 'flagged' : ''}</small>
 (???) /summary>
 (???) { item.text || '' }</details>`
 (???)        renderComments($comment.querySelector('details'), item.kids)
 (???)    }
 (???) 
 (???) onst item = item => {
 (???)    if (!item) throw Error('no such item')
 (???)    document.querySelector('.hn').innerHTML = `<article><header>
 (???) <h1><a href="${ item.url || '?id=' + id }">${ item.title || '' }</a></h1>
 (???) <small>by <a href="?user=${ item.by }">${ item.by }</a> <a href="?id=${ item.id }">${ formatter.format(new Date(item.time * 1000)) }</a> <a href="?id=${ item.parent }">${ item.parent ? 'parent' : '' }</a> ${item.dead ? 'flagged' : ''}</small>
 (???) /header>
 (???) {item.text || /* item.url || */ ''}</article>`
 (???)    const $article = document.querySelector('.hn article')
 (???)    renderComments($article, item.kids)
 (???) 
 (???) onst profile = user => {
 (???)    if (!user) throw Error(`no such user`)
 (???)    document.querySelector('.hn').innerHTML = `<section><h1>${ user.id }</h1>
 (???) <dl>
 (???)  <dt>About</dt><dd>${ user.about || 'N/A' }</dd>
 (???)  <dt>Submitted</dt><dd>${ user.submitted.length }</dd>
 (???)  <dt>Karma</dt><dd>${ user.karma}</dd>
 (???)  <dt>Created</dt><dd>${ formatter.format(new Date(user.created * 1000)) }</dd>
 (???) </dl>
 (???) /section>`
 (???) 
 (???) onst params = new URLSearchParams(document.location.search)
 (???) onst [id, user, type, query] = [params.get('id'), params.get('user'), params.get('page') || 'topstories', params.get('query')]
 (???) onst $nav = document.createElement('nav')
 (DOC) ocument.querySelector('header').appendChild($nav)
 (???) nav.innerHTML = `<ul class="${ type }">
 (???)    ${['topstories', 'beststories', 'newstories', 'showstories', 'askstories', 'jobstories']
 (???)        .map(i => `<li><a class="${ i }" href="?page=${ i }">${ i.replace('stories', '') }</a></li>`).join('')}
 (???) /ul>`
 (???) (async () => {
 (???)    try {
 (???)        if (!query) {      // TODO ignore query, try to render what you can... let plugins handle the rest
 (???)            if (id) {
 (???)                item((await fetchAll(`item/${ id }`))[0])
 (???)            } else if (user) {
 (???)                profile((await fetchAll(`user/${ user }`))[0])
 (???)            } else if (type) {
 (???)                await list(((await fetchAll(type))[0]).slice(0, 150))
 (???)            }
 (???)        }
 (???)    } catch (e) {
 (???)        console.error(e); document.querySelector('.hn').innerHTML = e
 (???)    }
 (???) )()