59 Matching Annotations
  1. Apr 2023
    1. Adhere to the team’s consensus-based process, but use just barely enough process. We should be aided by our tools and processes, not captive to them—it’s part of our job as a team to help find the right balance.
    1. Automatic unsubscribe footerIn Sending Defaults, select Add Unsubscribe Footer to automatically add the footer to your emails. This setting will apply globally, but if you’d rather include it conditionally, use the rules engine. The footer includes information about the address the email was sent to, along with an unsubscribe link. You do not need a website to process these unsubscribes—Mailchimp will direct the recipient to an unbranded web page confirming that the recipient address has been unsubscribed.
  2. Dec 2022
  3. Aug 2022
    1. Write clean code with minimal cleverness Comprehensibility and resilience far outweigh cleverness. Arcane solutions are not a badge of honor, and understanding your code shouldn’t be a hazing ritual. Don’t be afraid to be boring. New problems do not typically require new technology, nor new architectures. Consistent code helps us ship quickly and sensibly: common conventions and patterns speed everyone up. Experienced team members can understand and extend such code with greater ease, and new developers can get up to speed in a project faster.
    2. 1. Ship quickly, but sustainably Ship useful stuff to our users frequently, but responsibly, by way of small iterations and resilient conventions. Keep your changes small Small pull requests (PRs) lead to stronger code, faster iteration, sustainable velocity and a happier team. Smaller PRs with well-structured commits are a kindness to your reviewers, who benefit from a lighter context-switching demand. Code review cycles are speedier and of higher quality: reviewers are able to evaluate the entire change, quickly, without undue cognitive fatigue or LGTM-overwhelm. Smaller changes sharpen your focus, encouraging modular code with a purity of purpose. This in turn elicits clear code organization and can make coding and test-writing faster. Iterate, deploy, repeat Frequently ship iterative changes, using a reliable and non-intimidating deployment mechanism. Smaller releases make it easier to pinpoint a problem should something go awry, and are more straightforward to verify in our QA step. Deploying more often gets features and fixes to users fast, and avoids making giant leaps in (potentially) the wrong direction.
  4. Jul 2022
    1. Review with care and humanity Code reviews are business-critical—all production code requires review—and an important opportunity to support your teammates. Everyone involved, whether author or reviewer, has one core objective: help to move things forward. As a reviewer, you are a partner in the success of the body of work. Make code reviews a top priority, and be thorough and compassionate. Commit yourself when you review: focus and take your time. Avoid dismissive terms like “simply”, “just.” Ask questions. Discuss the code, not the coder. There are many conventions and techniques at play in high-quality code reviews, but all involve common underpinnings: be responsive, be committed and be kind. If you’re the code author, set your reviewer up for the best possible review cycle. Roll out the red carpet with a polished PR description. Do everything you can so that your reviewer can get down to the work of reviewing without fuss. Screenshots. Testing instructions. Comments to help them navigate. Remember to expect feedback—that’s the whole point—and to respond to it with the same humanity you expect from your reviewer.
  5. Mar 2021
    1. Don’t be a back-seat coder This means holding off, and not requesting many of the code changes that you may be tempted to request. Feedback that asks for too many changes and feels like a rewrite can be demoralizing, so consider your suggestions carefully and pick the best ones. Yes, one of the aims of code review is to find and correct bugs and design issues with the code. But don’t use code review to try to get the author to rewrite the code to the way you would have written it yourself. Remember that many things in software are a matter of opinion, multiple solutions that each have their pros and cons. For each “correction” that you want to suggest ask yourself whether it might be just a difference of opinion? In cases where the author has considered different solutions and chosen one solution for a reason, consider empowering the coder and respecting their right to make that decision.
  6. Jan 2021
  7. Oct 2020
  8. May 2019
    1. LTI + API Approach

      This approach would only work in Canvas

    2. Create an assignment in CanvasChoose “External Tool” as the Submission TypeChoose the tool from the list that appears in the modalA properly configured tool allows the instructor to then choose a resourceTool returns a content item message to Canvas with the Launch URL to that resourceTeacher saves and publishes the assignment

      We already have all 6 steps of this workflow

  9. Feb 2019
    1. OAuth 2.0 offers little to none code re-usability.

      ^ The lead author and editor of the OAuth 2.0 spec

  10. Aug 2018
    1. Return values of methods are still unspecified MagicMocks. For example the code won’t crash in the tests if it tries to call a method that doesn’t exist on the return value of an autospec’d mock’s method: >>> class Foo(object): ... def some_method(): ... return 23 ... >>> mock_foo = mock.create_autospec(Foo, spec_set=True, instance=True) >>> mock_foo.some_method() <MagicMock name='mock.some_method()' id='139778195517520'> >>> >>> # This should crassh but doesn't: >>> mock_foo.some_method().method_that_does_not_exist() <MagicMock name='mock.some_method().method_that_does_not_exist()' id='139778195121168'> Tests can fix this by specifying the return value: >>> mock_foo.some_method.return_value = 23 >>> mock_foo.some_method() 23 >>> mock_foo.some_method().method_that_does_not_exist() Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'method_that_does_not_exist' But now some duplication between the tests and Foo has been introduced. Again, if the real Foo.some_method() is changed then the above mock code would also need to be updated otherwise the tests for the code that uses Foo could still be passing even though the code is now wrong.
    2. Return values of methods are still unspecified MagicMocks. For example the code won’t crash in the tests if it tries to call a method that doesn’t exist on the return value of an autospec’d mock’s method: >>> class Foo(object): ... def some_method(): ... return 23 ... >>> mock_foo = mock.create_autospec(Foo, spec_set=True, instance=True) >>> mock_foo.some_method() <MagicMock name='mock.some_method()' id='139778195517520'> >>> >>> # This should crassh but doesn't: >>> mock_foo.some_method().method_that_does_not_exist() <MagicMock name='mock.some_method().method_that_does_not_exist()' id='139778195121168'> Tests can fix this by specifying the return value: >>> mock_foo.some_method.return_value = 23 >>> mock_foo.some_method() 23 >>> mock_foo.some_method().method_that_does_not_exist() Traceback (most recent call last): ... AttributeError: 'int' object has no attribute 'method_that_does_not_exist' But now some duplication between the tests and Foo has been introduced. Again, if the real Foo.some_method() is changed then the above mock code would also need to be updated otherwise the tests for the code that uses Foo could still be passing even though the code is now wrong.
    1. Don't compare boolean values to True or False using ==. Yes: if greeting: No: if greeting == True: Worse: if greeting is True:
    1. Note: The mock library actually has two very similar classes - Mock and MagicMock. The difference is that MagicMock supports Python’s magic methods whereas Mock doesn’t. This usually isn’t important, but as the mock user guide says it’s sensible to use MagicMock by default. The Hypothesis tests tend to use Mock more often, though.
  11. Sep 2017
  12. Aug 2017
    1. "My Happiness" was released as a single with "My Kind of Scene" as a B-side.

      Testing

  13. Jan 2017
    1. y American author Stephen Crane (1871–1900). The story takes place in the small, fictional town of Whilomville, New York. An African-American coachman named Henry Johnson, who is employed by the town's physician, Dr. Trescott, becomes horribly disfigured after he saves Trescott's son from a fire. When Henry is branded a "monster" by the town's residents, Trescott vows to shelter and care for him, resulting in h

      Annotation of page two

    1. The Jersey Act was introduced to prevent the registration of most American-bred Thoroughbred horses in the British General Stud Book. It had its roots in the desire of the British to halt the influx of American-bred racehorses of possibly impure bloodlines during the early 20th century. Many American-bred horses were exported to Europe to race and retire to a breeding career after a number of US states banned gambling, which depressed Thoroughbred racing as well as breeding in the United States. The loss of breeding records during the American Civil War and the late beginning of the registration of American Thoroughbreds led many in the British racing establishment to doubt t

      Annotation of page one

  14. Sep 2016
    1. How could we support “load more” in such situations?

      Yep, which is both a UI design and a technical implementation problem.

      If we've got all the replies in a separate annotation_replies db table, and one of the columns in that table is the ID of the thread root annotation that each reply belongs to, then it would be fairly easy to build on top of that an API for offset and limit paginating through a given annotation's replies, wouldn't it?

      Of course replies being an arbitrarily nested tree rather than a flat list complicates this, both in terms of the UI and the implementation.

  15. Aug 2016
    1. group:K1p4yo

      As mentioned in a reply to another annotation, I wonder if we might want the group IDs to be independent across annotation services like the user IDs are, something like group:xyz@hypothes.is.

      (I know that our current group IDs will be unique across all groups generated by our service, including third-party groups, but if there are one day real third-party annotation services run by other people I don't think we'll be able to rely on that, we'll have to namespace groups.)

    2. The URL fragment also contains the domain of the annotation service, hypothes.is, as a namespacing element.

      So this would be hard-coded into the client code, or be part of the build-time client code configuration, like #annotations is now? i.e. the client would look for a #hypothes.is... fragment to decide whether to activate itself?

      Just wondering what a "generic" client, that does not belong to any one particular annotation service but can work with many, would use as its top-level fragment namespace. It seems like the annotation client itself would need its own name for this purpose, #my-universal-annotation-client:.... Or use the domain name of the client's website where you can download the client from (which is not necessarily an annotation service website, just like mutt.org is not an email service website).

      (I think #hypothes.is:... is fine for our client, though.)

  16. Feb 2016
    1. Nautilus Type-Ahead Find Feature Enabled By Default In Ubuntu 14.04

      How to enable type ahead find ("interactive search") / disable recursive search in Nautilus (this works for me in Nautilus 3.14.2 on Ubuntu Gnome 15.10): just check org.gnome.nautilus.preferences.enable-interactive-search in dconf-editor, or in a terminal just do:

      gsettings set org.gnome.nautilus.preferences enable-interactive-search false
      

      I much prefer the interactive search version, because its a really fast way to navigate with the keyboard by typing the first couple of letters of a file or folder name to jump the selection to that file or folder. For example I open my home folder and type dr and the selection moves to my Dropbox folder then I type Enter to go into the Dropbox folder.

      With recursive search typing dr would replace the display of all top-level files and folders in my home dir with a display of all files and folders matching a recursive search for dr starting from my home dir, which is useful if you're trying to find something and you don't know where it is, but much less useful if you know where you're going and want to navigate quickly with the keyboard.

      With interactive search enabled you can still do a recursive search by typing Ctrl-f first.

    1. [New Requirement: When there's activity for you somewhere, indicate it?]

      This whole problem where he couldn't find his group's annotations because he was focused on the public group - it seems to argue for the All view that Dan has been pushing for, doesn't it?

      As the default view. The groups (or "scopes") dropdown then becomes a power-user feature rather than an absolutely-necessary-to-even-see-the-annotations feature - you only need to use the dropdown when you want to filter it to show only some annotations and not others.

    1. .then((res) => {

      Please, just type result.

    2. window.fetch('/api/v1/users')

      Just fetch() also seems to work.

    3. Testing API requests from window.fetch

      Sinon.js's fakeServer works for XMLHttpRequest but not for window.fetch(). Instead, window.fetch() is really easy to stub with sinon.stub().

  17. Jun 2015
    1. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

      This is a nonce (number used only once), a different one is generated by the client for each WebSocket connection it tries to make.

      Also, browser will only allow HTTP headers beginning with Sec- to be sent by using certain APIs such as the WebSocket API. Some bad JavaScript running in a browser and submitting forms or sending XMLHttpRequests can't send this header, so it can't open a WebSocket.

    2. 1.6. Security Model

      This leaves me wondering how login/authorization to websites works over WebSocket. Do clients include a session cookie or authorization header in the opening HTTP handshake, and that user ID is then established for the life of the WebSocket connection?

      What about the privacy of the messages sent down the WebSocket - do people layer encryption on top of the WebSocket protocol?

    3. Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

      This number is computed based on the nonce sent by the client (see later for exactly how), so it proves to the client that the server received its handshake request.

    4. If the server does not wish to accept connections from this origin, it can choose to reject the connection by sending an appropriate HTTP error code

      So the WebSocket server decides what origins it'll accept WebSocket connections from and for example could choose only to connect with JavaScript code served from its own domain.

    5. The server is informed of the script origin generating the WebSocket connection request.

      WebSocket is an API provided by browsers to the JavaScript code running in the browsers. So when some JavaScript calls the API to make a WebSocket connection, the browser will tell the client what the origin of that JavaScript is. The JavaScript code can't just trivially claim to be from some other origin.

  18. Apr 2015
    1. hypothesis.js

      hypothesis.js is injected into the page by embed.js using either the browser's plugin API or (in the case of the bookmarklet) the DOM API. (embed.js was in turn injected by the browser plugin or bookmarklet).

      hypothesis.js is the "bootstrap" code that connects up and starts the various components of the Hypothesis app.

    2. app: jQuery('link[type="application/annotator+html"]').attr('href'),

      Here we find the <link rel="sidebar" ... that embed.js injected into the page. We pass it into the constructor method of Annotator.Host below.

    3. window.annotator = new Klass(document.body, options);

      Calling the Annotator.Host construct, passing an options object including our sidebar link.

    4. Annotator.noConflict().$.noConflict(true);

      Having created our Annotator instance and added our custom plugins etc to it, we inject Annotator into the page.

    1. layout.app_inject_urls

      app_inject_urls is the list of scripts and stylesheets that we're going to inject into the page. This comes from layouts.py, which in turn gets it from assets.yaml.

      Most importantly these URLs to be injected include a minified version of hypothesis.js.

    2. var baseUrl = document.createElement('link'); baseUrl.rel = 'sidebar'; baseUrl.href = '{{ app_uri or request.resource_url(context, 'app.html') }}'; baseUrl.type = 'application/annotator+html'; document.head.appendChild(baseUrl);

      Finally, we inject a <link rel="sidebar" type="application/annotator+html" href=".../app.html"> into the <head> of the document. This is the HTML page for the contents of the sidebar/iframe. This link will be picked up by hypothesis.js later.

    3. if (resources.length) { var url = resources.shift(); var ext = url.split('?')[0].split('.').pop(); var fn = (ext === 'css' ? injectStylesheet : injectScript); fn(url, next); }

      This loop is where we actually call injectScript() or injectStylesheet() on each of the resource URLs defined above.

    4. var injectScript = inject.script || function injectScript(src, fn) {

      And we do the same thing for injecting scripts as we did for injecting stylesheets - we either use the function passed in by the browser plugin, or when called by the bookmarklet we fall back on the DOM API.

    5. var injectStylesheet = inject.stylesheet || function injectStylesheet(href, fn) {

      hypothesisInstall() will use the inject.stylesheet() function passed in to it to inject stylesheets into the page or, if no function was passed in, it'll fallback on the default function defined inline here.

      The default method just uses the DOM's appendChild() method, but this method may fail if the site we're trying to annotate uses the Content Security Policy.

      That's why when we're using one of the browser plugins rather than the bookmarklet, we pass in the browser API's method for injecting a stylesheet instead.

      This is why the bookmarklet doesn't currently work on GitHub, for example, but the Chrome plugin does.

    6. embed.js

      embed.js is responsible for "embedding" the different components of the Hypothesis frontend application into the page.

      First, either bookmarklet.js or one of the browser plugins injects a <script> tag to embed.js into the page, then embed.js runs.

      This way the code in embed.js is shared across all bookmarklets and browser plugins, and the bookmarklets and plugins themselves have very little code.

    1. app.appendTo(@frame)

      And we inject our <iframe> into ... the frame? (@frame is a <div> that wraps our <iframe>, it's defined and injected into the page in guest.coffee).

    2. app = $('<iframe></iframe>') .attr('name', 'hyp_sidebar_frame') .attr('seamless', '') .attr('src', src)

      Finally, this is where we create the <iframe> element that is the Hypothesis sidebar!

    1. embed = document.createElement('script'); embed.setAttribute('src', embedUrl); document.body.appendChild(embed);

      Here we construct the actual <script> element, set its src URL, and inject it into the page using the DOM's appendChild() method.

    2. var embedUrl = '{{request.resource_url(context, "embed.js")}}';

      The whole job of the bookmarket is to inject a <script src=".../embed.js"> element into the current page. The src URL of this script element points to embed.js, another Pyramid template rendered by the server-side Hypothesis app.

    3. bookmarklet.js

      bookmarklet.js is the Pyramid template (rendered by our server-side Pyramid app) for the Hypothesis bookmarklet. This little bit of JavaScript (after being rendered by Pyramid) is what the user actually drags to their bookmarks bar as a bookmarklet.

  19. Mar 2015
    1. Angular Style Guide: A starting point for Angular development teams to provide consistency through good practices.

      This is a fantastic and very detailed AngularJS style/best practices guide. Used by hypothesis/h.

    2. A Vim plugin for visually displaying indent levels in code

      Great Vim plugin!

      vim-indent-guides plugin screenshot

    3. def create_api(global_config, **settings):

      The separation isn't complete yet, but it's our aim to split Hypothesis into two separate Pyramid apps: the API and the frontend.

      create_app() above calls this create_api() function to add the API features into the app if h.feature.api is True in the config file. If it's False you'll run the frontend only with no API.

      There'll also be an h.api_url setting that you can use to tell the frontend app where to find the API. If h.feature.api is True then you don't need h.api_url, the frontend can just use its builtin API. But if it's False then you can use h.api_url to tell your frontend app to work with an API hosted elsewhere.

    4. return config.make_wsgi_app()

      Finally, we return the configured WSGI app object that Pyramid will call on to respond to any HTTP requests that come in. This is an instance of a Pyramid-provided class that implements the WSGI interface.

    5. if config.registry.feature('accounts'):

      Some Hypothesis features can be turned on and off in the config file.

    6. config.include('.features')

      config.include() is Pyramid's way of doing extensible apps. It looks for an includeme() method in features.py (or in features/__init__.py if features were a package) and calls it, passing in a configurator object.

      Since includeme() functions get called by Pyramid at startup time they can run any code they want at application startup - for example to verify the values of config settings and crash if they're invalid.

      includeme() functions can add anything they want to the config, including routes, views, and subscribers.

      In Hypothesis we try to implement as many features as possible in standalone modules or packages and include them like this.

    7. This means that when Pyramid triggers its BeforeRender event our add_renderer_globals() function will be called. (See Pyramid Events).

    8. config.set_root_factory('h.resources.create_root')

      The root factory is a function that returns the root object for Pyramid's traversal tree. (The traversal tree is how Pyramid maps URLs to view callables.)

    9. config = Configurator(settings=settings)

      A Pyramid app is configured by creating a Configurator object and setting configuration options on that. We'll later use the Configurator object to make a WSGI app with our configured settings.

    10. def create_app(global_config, **settings):

      This function is referenced as the "main" app factory function in setup.py. Pyramid calls this function at boot to create the main WSGI app.

    11. """The main h application."""

      app.py is the "main" file of the Python app. If you look in setup.py you'll see that the 'paste.app_factory' entry points point to functions in app.py. This tells Pyramid to call these functions to create the app when it starts up.