freenode :: #whatwg

5 Jan 2017
02:18MikeSmithbotie, inform smaug the role of authoring roles in the HTML spec is to help authors catch mistakes they might have otherwise missed. They are essentially standardized linting/static-analysis checks. See https://checker.html5.org/about.html#why-validate
02:18botiewill do
02:21MikeSmithbotie, inform smaug for making HTML-checking tools useful it is best that we have a common normative set of rules to check. That is what the spec provides. Without that we instead just have makers of checking tools basing the checks on whatever whims or personal preferences they wantlike what the situation is with some JavaScript linting tools.
02:21botiewill do
02:23tantekMikeSmith++
02:36jwaldenis "real 2m6.762s" actually unsurprising for the time it takes to |git annotate| around thirty lines of the HTML spec? :-\
02:37jwalden'cause that's what I got for |time git annotate -L94793,94827 source| on the spec just now
02:58jwaldenaaaaugh so many restructurings making annotation a long series of manual jumpbacks
03:55MikeSmithjwalden: fwiw what I do is, I dumped `git log -p` output to file and I just do string searches through the to trace changes
03:56MikeSmithit is still a PITA even doing it that way, but for me at least less of PITA than trying to use git annotate or any of the wrappers around it for doing recursive blames
03:57MikeSmithon my machine it takes like 3-4 minutes or so to (re)dump out the full log
03:57MikeSmithand the log is not so huge, about 90M
06:35annevkgit log --grep ftw
07:07hsivonenfoolip: do you happen to recall why the index for Big5 in the Encoding Standard has duplicate entries instead of using CJK Compatibility Ideographs for what would otherwise be duplicates? (The other CJK indices use CJK Compatibility Ideograph code points for what would logically be duplicates.)
07:12hsivonenannevk: or do you happen to recall why?
07:14annevkhsivonen: no, sorry
07:15timdreamhsivonen: what are the examples to your question
07:21hsivonentimdream: hmm. finding an example is harder than I expected. Maybe that's my answer. Still trying.
07:24hsivonenhmm. Lunde says there are supposed to be two doubly-mapped characters in Big5, but I see many more in index-big5
07:27zcorpancan someone with Edge test what it does for https://github.com/whatwg/html/issues/2174#issuecomment-269965310 ?
07:34hsivonentimdream: so it turns out that the two characters that I infer Lunde refers to are U+FA0C and U+FA0D. and these two do appear in index-big5
07:35hsivonenbut then there are *other* duplicates
07:35hsivonennow I don't know if the other duplicates arise from the HKSCS merge or if they arise from other reasons
07:36hsivonenAccording to Lunde and Unicode, the unextended Big5 is not supposed to have as many duplicates as index-big5 has
07:36timdreamhsivonen: i think so. no idea, might have some if could give me some code points
07:37hsivonenU+89A6 and U+799B to pick a couple at semi-random
07:38timdreamhsivonen: i was told the big5-to-unicode table should be based on big5-hkscs and unicode-to-big5 is strictly the original CP950
07:38hsivonentimdream: anythig with a red border in https://hsivonen.fi/encoding-visualization/big5.html
07:38timdreamhsivonen: looking
07:40hsivonenI spot-checked some of the red-border characters, and they don't seem to have corresponding Compatibility Ideographs defined, so as far as Compatibility Ideographs go, there's nothing amiss after all
07:42timdreamhsivonen: hum, i was wrong, encoding standard (an re-impl in Gecko) removes the asymmetrical mapping of Big5
07:43hsivonentimdream: there's an asymmetry: pointers below a certain threshold are ignored by the encoder
07:43timdreamhsivonen: ok
07:43timdreamhsivonen: sorry, i still have no idea on your duplication question though.
07:44hsivonentimdream: ok. thanks
07:44timdreamhsivonen: would it be helpful if i try to find someone that could extract or run the legacy ET3 Chinese DOS system?
07:45timdreamhsivonen: maybe take a screenshot of these "duplicate" glyphs...
07:45hsivonentimdream: no need. I was just curious. I don't have any real indication of there being an actual problem.
07:46timdreamhsivonen: but that does mean Unicode did not achieve round-trip compatibility with Big5: https://www.wikiwand.com/en/CJK_Compatibility_Ideographs
07:48hsivonentimdream: It seems to me it did with unextended Big5, but that's not what the Encoding Standard has.
07:50timdreamhsivonen: the community have collected a few mapping table here https://moztw.org/docs/big5/ ... at least in CP950 I don't see U+89A6 being mapped twice
07:51timdream(yet it got mapped twice in https://moztw.org/docs/big5/table/hkscs2001-b2u.txt )
07:53hsivonentimdream: OK. chances are that the duplicates come from HKSCS then
07:54* timdream (sad face)
07:57timdreamhsivonen: yeah at least from your example I can see that CP950 does not map byte sequence 0x8F 0xCB while HKSCS do
07:58hsivonentimdream: OK.
08:18annevkhsivonen: what foolip did is basically a merge between "normal" Big5 and HKSCS, resolving the conflicts between the two somehow and maybe avoiding PUA here and there
08:18annevkhsivonen: it's described in an email somewhere
08:27annevkmounir: https://notifications.spec.whatwg.org/#create-a-notification seems to do an eager parse for action icon URLs
08:28annevkmounir: I hadn't actually looked at the specification yesterday, but it seems that's covered
08:41timdreamannevk: having a link to that e-mail will be a great help allowing me to amend the mentioned docs :)
08:42timdreamannevk: that document still says "a new feature was landed in Mozilla 1.8" for example
08:42MikeSmithzcorpan: glad to see https://github.com/whatwg/wattsi/pull/39 getting OKed
08:43annevktimdream: I can have a look
08:44zcorpanMikeSmith: yep. maybe one day i can try to fix the followup
08:45annevktimdream: https://lists.w3.org/Archives/Public/public-whatwg-archive/2012Apr/thread.html#msg42
08:45timdreamannevk:
08:45MikeSmithzcorpan: Yeah if you want I can make time to look into changing it to use Hixies Ropes stuff, but given that he says its not a problem as-is, it is hard to prioritize that very high
08:46annevktimdream: https://lists.w3.org/Archives/Public/public-whatwg-archive/2012Mar/thread.html#msg259 also
08:46MikeSmithzcorpan: but I guess we should at least take time to do some rough benchmarking with and without this change to find out if it actually introduces any measurable slowdown to the build time
08:46annevktimdream: and https://lists.w3.org/Archives/Public/public-whatwg-archive/2012Apr/thread.html#msg95 which somehow is a separate thread
08:47zcorpanMikeSmith: my thoughts as well, trying to time generating the spec now :-)
08:48MikeSmithsuper
09:15zcorpanMikeSmith: https://github.com/whatwg/wattsi/issues/40#issuecomment-270597675
09:16* MikeSmith looks
09:18MikeSmithzcorpan: cool!
09:38annevkjgraham: ever encountered something like https://github.com/w3c/web-platform-tests/pull/4378#issuecomment-270597694 before?
09:39annevkI'm not really sure what to think of it
09:45jgrahamannevk: There shouldn't be any console logging in tests
09:45annevkjgraham: there isn't
09:46annevkjgraham: Chrome prints test results to the console I suspect, but the console can also contain other bits
09:46annevkjgraham: maybe the problem is their testharness though
09:55jgrahamYeah, I think that a system where races in log message printing can break your test output is broken by design
10:04annevkJakeA: in https://github.com/whatwg/fetch/issues/447 you reference the service worker case a few times, but it's not explained
10:05annevkJakeA: oh I see, it's about whether the page did something
10:20JakeAannevk: I'll add more detail
10:21annevkJakeA: it kinda makes sense to expose it on FetchEvent, or maybe make respondWith reject
10:22JakeAannevk: It seems like we need channels for anything "request modifying". Eg, if I try to change the priority of a request from a page, how do I hear about that in the service worker?
10:25annevkJakeA: I recommend saying "fetch modifying"
10:26annevkJakeA: and yeah
10:28annevkJakeA: becomes more like a ControlToken then or a FetchToken
10:29annevkJakeA: which you can either propagate or ignore
10:29JakeAannevk: yeah, if a fetch() gets a controller, a fetch event needs a controller observer
10:43annevkJakeA: yeah, it wants to observe, but it also wants to propagate to any fetches of its own (and perhaps any cache/db queries)
10:43annevkJakeA: but maybe just observing is enough and the rest can be a library, but then observing could also be a library (through postMessage)
10:44JakeAannevk: controllerObserver.pipeTo(controller) kinda thing
11:18JakeAannevk: what about: const fetch = new Fetch(request); fetch.connected.then(response => )
11:19JakeAI'm trying to avoid having to pass the controller back out of a revealing function
11:23annevkJakeA: that's reasonable, though I believe Domenic doesn't want new constructors with side effects
11:23annevkJakeA: so you'd need fetch.fetch() or requivalent
11:23JakeAannevk: controllableFetch() then
11:23JakeAhah
11:24JakeAannevk: although that brings me back to just making fetch() return a thennable controller
11:24annevkyeah, I still like that the best
11:24annevkotherwise it becomes a third network API...
11:41noxWhy is document.open's spec weirdly written?
11:41noxAnd by that I mean that it doesn't piggyback WebIDL as much as the rest of the HTML spec,
11:41noxsaying stuff like "7. Let type be the value of the first argument."
11:45annevknox: old
11:45noxOk.
11:45annevkI have a half-hearted patch somewhere
11:46annevkBut it's not complete and I noticed so many problems along the way I just moved on to other things
11:46mounirannevk: any reason why you didn't leave a comment regarding the freeze question?
11:47annevkmounir: pointer?
11:48annevkProbably missed it
11:48annevkI only get one notification per thread and I didn't read the whole thing
11:48mounirannevk: https://github.com/WICG/mediasession/pull/162#discussion_r94619550
11:50botiesmaug, at 2017-01-05 02:18 UTC, MikeSmith said: the role of authoring roles in the HTML spec is to help authors catch mistakes they might have otherwise missed. They are essentially standardized linting/static-analysis checks. See https://checker.html5.org/about.html#why-validate and at 2017-01-05 02:21
11:50botieUTC, MikeSmith said: for making HTML-checking tools useful it is best that we have a common normative set of rules to check. That is what the spec provides. Without that we instead just have makers of checking tools basing the checks on whatever whims or personal preferences they wantlike what
11:50botiethe situation is with some JavaScript linting tools.
11:51smaugMikeSmith: but why authoring rules are different to the spec for implementers?
11:51smaugCouldn't those be the same
11:52MikeSmithbecause implementors still have to deal with cases where authors don'
11:52MikeSmithbecause implementors still have to deal with cases where authors dont follow the rules
11:52MikeSmithanyway I agree it is confusing to implementors to have authoring rules in the spec
11:53MikeSmithbut the only alternative is to move them all out to a separate spec, which also has big downsides
11:55smaugof course I feel embarrassed when I happened to read authoring rules, but that was the first thing find-in-the-page gave me as result when looking at usemap stuff. I should have looked at that part was in 'authoring'.
11:57MikeSmithsmaug: you are not the first implementor who the spec has confused that way and you wont be the last
11:58MikeSmithIMHO in some ways we are getting the worst of both worlds by conflating UA implementation requirements and author requirements into the same spec
11:59MikeSmiththe spec is rightly optimized essentially for that needs of implementors, because that it what functional specs are for, and thats what the greatest need is
11:59smaugI don't understand why the rules for authoring can be so different. I mean, in this case at least authoring rules could easily just follow what implementations should do
12:00smaugthe less authoring requirements and implementation requirements differ, the better, I think
12:00MikeSmithwell there are a lot of cases where we make the author rules stricter than what UAs allow
12:00smaugwhy?
12:00annevksmaug: well for one, a couple implementations don't support id attributes
12:00MikeSmithsure I agree with you ideally that is how it should be
12:01smaugwhy authoring rules need to be stricter ?
12:01annevksmaug: so making developers stick to name will make more stuff work
12:01MikeSmithsmaug: yes, because of https://checker.html5.org/about.html#why-validate
12:01annevksmaug: and will also be more backwards compatible
12:01MikeSmith> There are some markup cases defined as errors because they are potential problems for accessibility, usability, interoperability, security, or maintainabilityor because they can result in poor performance, or that might cause your scripts to fail in ways that are hard to troubleshoot.
12:01mounirannevk: thanks :)
12:02MikeSmith> Along with those, some markup cases are defined as errors because they can cause you to run into potential problems in HTML parsing and error-handling behaviorso that, say, youd end up with some unintuitive, unexpected result in the DOM.
12:04annevkJakeA: we can't subclass promise? Or would then() then return the wrong thing?
12:04JakeAannevk: subclassedPromise.then() creates a new promise using subclassedPromise.constructor
12:04annevkJakeA: and we cannot override that?
12:05JakeAannevk: nope. There used to be a @@species thing in the spec to allow you to do that, but it's gone
12:06annevkJakeA: @@species is still there, but I guess you're saying promises no longer use it?
12:06annevkJakeA: https://tc39.github.io/ecma262/#sec-get-promise-@@species suggests it's still possible
12:07JakeAannevk: oh wow. I wonder if it vanished & returned, or if I misunderstood
12:13JakeAannevk: updated the comment to use @@species
14:44Ms2gerannevk, has anyone looked at whatwg.org recently?
14:45annevkMs2ger: it's known, but no solution
14:45Ms2gerOkay, thanks
14:45annevkMs2ger: apparently foolip setup monitoring software
14:45annevkMs2ger: but we don't have repair software
14:46foolipI shouldn't be that hard to have better uptime than this...
14:47annevkWe have multiple offers for sysadmins now...
14:47foolipuhum. if not for the wiki, would it be entirely static?
14:47foolipI guess there's the webhooks too
14:48annevkThe webhooks are slowly being replaced with software that runs on Travis and uses scp to copy files over
14:50fooliphsivonen: Encoding's Big5 is essentially just the union of what implementations did at the time, I think typically siding with Big5-HKSCS in case of conflict. If there are duplicates, that's probably because implementations of Big5-HKSCS already did, but it's possible things have changed since.
14:50foolipIf I&#39;m not mistaken, the decoder is a superset of Big5-HKSCS, with no or very few (<5) exceptions.
14:57* jgraham wonders if foolip&#39;s grand plan involves making github pay for whatwg&#39;s bandwidth
14:58foolipjgraham: not really, but for just hosting static content, one doesn&#39;t have to do much thinking, just find a provider with high reliability.
15:18zcorpanany opinions on https://github.com/whatwg/html/pull/2236 ? i&#39;m not a color profile expert but it seems to me that the first iteration with darker colors were more correct (since they matched SVG colors and XhmikosR&#39;s comments)
15:20Domenicfoolip: annevk: some of the demos use CGIs, and we use .htaccess a lot
15:20foolipDomenic: ok, so no quickfix then :)
15:21DomenicWe could lose the CGIs without much sadness, but not sure what the contemporary alternatives to our htaccess funtimes are.
15:21DomenicE.g. does nginx have an equivalent?
15:22annevkYeah, we&#39;d have to do some configuration differently
15:23jgrahamThe ngix docs say &quot;.htaccess is horrible for performance, don&#39;t use it&quot;
15:25gsneddersDon&#39;t we have enough people from Google working on WHATWG specs? Doesn&#39;t someone at Google know something about hosting a site? :)
15:28DomenicAs far as I can tell nginx wants you to use a single config file per server
15:28DomenicWhich is definitely faster but is kind of sad in that we can no longer check them into the repos
15:28DomenicI guess we could check them into a separate repo
15:30boogymanYou could create a single template and upon startup render the appropriate config
15:32wanderviewannevk: JakeA: what would fetch() look like if we designed it today? would we return a Fetching object with a Fetching.response promise for what is currently returned by fetch()?
15:33wanderviewif we can agree on what we would like it to look like, maybe we can find the least painful way to backfill to current API
15:34JakeAwanderview: for me, fetch() would return a fetching object that&#39;d have a .responded (or something) promise that&#39;d resolve with what fetch() resolves with today
15:34wanderviewJakeA: ok, lets just make fetch2() then!
15:34JakeAwanderview: that&#39;s why I&#39;m keen on adding the controller stuff to the promise that fetch() returns
15:34JakeAhaha
15:35wanderviewJakeA: I mean, other than some embarrassment, what are the reasons not to add another function that does what we want? then we have &quot;simple fetch&quot; and &quot;advanced fetch&quot;
15:36wanderviewJakeA: it feels icky to add stuff onto the promise... seems like it will break with any aggregation with promise tools like Promise.all(), etc
15:36wanderviewI mean, it feels like the &quot;promise type that does what we want&quot; has sailed
15:37JakeAwanderview: ^ see above where I suggested controlledFetch()
15:37JakeAwanderview: but the promise subclass thing works find with Promise.all() - you just get a regular promise in that case
15:37wanderviewJakeA: oh... backscroll... I&#39;ll look!
15:38JakeA(there&#39;s not much to see)
15:38annevkDomenic: yeah, we&#39;d put the whole thing in GitHub
15:38wanderviewJakeA: oh, I don&#39;t see the controlledFetch() thing
15:39annevkDomenic: going down that route is an advantage in some way, since we can just switch hosts whenever, since all the config is accounted for
15:39JakeAwanderview: ah, it was controllableFetch()
15:40wanderviewah, I see
15:41wanderviewJakeA: oh, so &quot;putting it on the promise&quot; is really just returning a FetchController with a .then() that returns a Response... it could be an alias for FetchController.responded.then()
15:42JakeAwanderview: yeah, but not sure what the benefit in the alias would be
15:42wanderviewJakeA: it makes me feel better to think about it from it starting as a different type and using Promise .then() compat feature
15:42wanderviewJakeA: instead of having a Promise prototype with extra gunk added on
15:43JakeAwanderview: that would work (http://jsbin.com/zopaya/edit?js,console), but seems a bigger change as instanceof Promise is no longer true
15:44annevkwanderview: FetchController could just be a subclass of Promise
15:45wanderviewok
15:45annevkwanderview: the reason to avoid a new API is because that has a high cost (lots of additional tests, everyone has to relearn, existing code needs lots of conditionals, etc.)
15:45wanderviewJakeA: I&#39;m not going to comment on the issue because its too long already... but this sound good to me
15:50JakeAannevk: in terms of the subclass, that&#39;ll upset people who didn&#39;t like promise.cancel(), as the person they pass the promise to can control the fetch. But they can already control the response so I don&#39;t think it&#39;s a huge deal.
15:50JakeAAnd they&#39;re one .then() away from blocking that if they want.
15:50annevkJakeA: yeah, just give .then() to your consumers
15:51JakeAannevk: well, seems like we&#39;re in agreement. We just have to convince the rest of the world :D
15:56annevkhah
15:59wanderviewJakeA: annevk: I think we have to accept some people will not be happy no matter what
15:59JakeAgeneral life advice
15:59wanderviewJakeA: you could go the Trump route and troll them in the process, because why not?
15:59wanderview(probably not a good idea)
15:59JakeAhahaha
16:09hsivonenfoolip: thanks
16:21mathiasbynenswtf, `document.querySelectorAll(&#39;a[href^=&quot;h&#39;)` (note: missing `&quot;]`) works somehow?
16:23XhmikosRfor what is worth, there&#39;s no reason to jump to nginx. A properly configured Apache site will be pretty fast nowadays
16:24* Ms2ger puts `body { color: green` in mathiasbynens&#39;s CSS
17:58gsneddersmathiasbynens: there&#39;s a bug on csswg-drafts raised by me on that, works by design apparently
17:59DomenicSpecs are back, for anyone who was waiting
17:59gsneddersmathiasbynens: https://github.com/w3c/csswg-drafts/issues/492
19:09annevkDomenic: https://github.com/w3c/wptserve/issues/80
19:49Domeniczcorpan: do the optimized images look different on your screen? Now that we&#39;ve deleted the SVG-to-PNG conversion ones, all that remain look the same to me.
19:49zcorpanDomenic: they look the same to me in the current state of the PR
19:50Domeniczcorpan: so we should probably merge then?
19:50zcorpanDomenic: they looked different in the first iteration, before XhmikosR force-pushed new commits to make them look the same
19:50zcorpanDomenic: well the point is that the current images appear to be bogus
19:50zcorpanDomenic: but i don&#39;t care strongly either way
19:51DomenicOh I see hmm
19:51DomenicWell I guess that&#39;s a separate issue
20:01TabAtkinsmathiasbynens: When you feed that string to a spec-conforming CSS parser, all open constructs get closed by EOF.
21:48wanderviewJakeA: annevk: should we put FetchController as an attribute on the Response in addition to returning it from fetch()? So that existing code that does an immediate .then() can still get access to it easily after the headers arrive
22:01smaugjsbell: I&#39;m trying to understand the upgrade transaction
22:01smaugwhen is the commit supposed to happen?
22:01smaug(in general &quot;commit&quot; seems to be quite vaguely defined in the spec)
22:02smaugis it basically in step 10 in http://w3c.github.io/IndexedDB/#steps-for-running-an-upgrade-transaction
22:20jsbellsmaug: tuning in, give me a second...
22:20jsbellsmaug: implicitly in step 8 - waiting for transaction to finish
22:21smaugright, so ok, in before step 10
22:21jsbellyeah.
22:21smaugjsbell: btw, I just updated https://github.com/w3c/IndexedDB/issues/87#issuecomment-270766086
22:21jsbellAh, &quot;immediately&quot; ...
22:21smaugI don&#39;t know how blink behaves with upgrade
22:22smaugdoes it still somehow use after-microtask model there?
22:22smaug(if so, where)
22:22jsbellupgrades do not use the after-microtask. that&#39;s only needed for db.transaction() calls
22:22jsbelldb.transaction()-minted transactions are created active, upgrade transactions are created inactive
22:23smaugjsbell: ok, good. Gecko has still somehow this after-promise model with upgrade, but I had to remove it to make tests pass when making promises to use microtasks
22:24smaugso, after-microtask with db.transactions(), but more explicit handling with upgrades
22:25jsbelland yeah, that step 10 should be rewritten - it&#39;s like a monkey patch within the spec. :P Ah well, still better than v1.
22:28jsbellthat step 10 should really be a modification to http://w3c.github.io/IndexedDB/#commit-transaction step 3 and http://w3c.github.io/IndexedDB/#abort-transaction step 5, something like: &quot;#. Queue a task to run the following substeps. 1. If /transaction/ was an upgrade transaction, let /request/ be the request associated with /transaction/ and set /request/&#39;s transaction to null. 2. Dispatch an event at /transaction/....&quot;
22:30jsbellsmaug: I&#39;m curious about what&#39;s special about the upgrade transactions vs. microtasks.
22:31smaugjsbell: well, in which microtask the upgrade transaction would be committed or so
22:32smaugupgrade transaction somehow implicitly happens asynchronously
22:32smaugduring possibly multiple tasks
22:33jsbellsmaug: Fun. Ah. Shouldn&#39;t be different than any other transaction. After dispatching a request&#39;s success/error event (which implies running pending microtasks), when setting the transaction to inactive, if the transaction has no more requests then try and commit.
22:34jsbellThat&#39;s buried in the prose in http://w3c.github.io/IndexedDB/#transaction-lifetime-concept step 7 (a maze of twisty implications that all look alike.)
22:35jsbellThe exact timing shouldn&#39;t matter, since it can&#39;t happen while the transaction is active, and once the transaction goes inactive in that state it can implicitly never be active again.
22:36jsbell... and any result of the commit attempt is a &quot;queue a task...&quot;
22:44jsbellUh, swap 1/2 in the substeps I gave above.
23:03Mekhmm, there is explicitly no synchronization of any kind in (Local)Storage, right? So code like `for (let i = 0; i < localStorage.length; ++i) console.log(localStorage.key(i));` could easily have the storage be modified somewhere in the middle of iterating?
23:05TabAtkinsCorrect. The spec originally had basically a global lock, but nobody implemented it, and it was eventually dropped. localStorage is just as racy as the cookie store.
23:08gsneddersAFAIK there&#39;s a &quot;SHOULD&quot;-level requirement to have a lock, with the expectation that nobody ever will. But, well
23:12MekI&#39;m not sure how a lock would even help with multiple consecutive operations? But yeah, makes sense that it doesn&#39;t have any such requirements (although it could have some kind of requirements to expect some consistency between the exposed state and the storage events that have or haven&#39;t been fired yet... but I&#39;m glad there isn&#39;t such a requirement)
23:12TabAtkinsMek: I think you get a lock until you return to the event loop (thus maintaining JS&#39;s run-to-completion semantics for the data)?
23:12TabAtkinsThat is to say, I think I remember that being how the lock worked.
23:13Mekah yeah, I suppose that might work (for some deifnition of work)
23:16gsneddersIIRC Firefox has(? had?) a lock?
23:17gsneddersbut with most JS running in a single thread anyway that&#39;s far less of an issue
23:55gsneddersDo meta redirects in the <body> have an affect?
6 Jan 2017
No messages
   
Last message: 111 days and 21 hours ago